From 9528766ea8681882007970b572dafea0d75c1dec Mon Sep 17 00:00:00 2001 From: nxqbao Date: Fri, 19 Aug 2022 13:28:46 +0700 Subject: [PATCH 001/190] Init commit --- .env.example | 7 + .gitignore | 12 + .prettierrc.json | 21 + hardhat.config.ts | 72 + package.json | 55 + pull_request_template.md | 6 + tsconfig.build.json | 20 + tsconfig.json | 13 + yarn.lock | 9434 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 9640 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .prettierrc.json create mode 100644 hardhat.config.ts create mode 100644 package.json create mode 100644 pull_request_template.md create mode 100644 tsconfig.build.json create mode 100644 tsconfig.json create mode 100644 yarn.lock diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..d8f3bdf35 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# Testnet configs +TESTNET_PK=0x0000000000000000000000000000000000000000000000000000000000000000 +TESTNET_URL=https://testnet.skymavis.one/rpc + +# Mainnet configs +MAINNET_PK=0x0000000000000000000000000000000000000000000000000000000000000000 +MAINNET_URL=https://api.roninchain.com/rpc \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..906949bb6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +artifacts/ +cache/ +node_modules/ +src/types/ +src/web3-types/ +dist/ +yarn-error.log +.env +.idea + +refs/ +.vscode \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..7d7e4fa5b --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,21 @@ +{ + "printWidth": 120, + "tabWidth": 2, + "useTabs": false, + "bracketSpacing": true, + "explicitTypes": "always", + "overrides": [ + { + "files": "*.sol", + "options": { + "singleQuote": false + } + }, + { + "files": "*.{js,ts}", + "options": { + "singleQuote": true + } + } + ] +} diff --git a/hardhat.config.ts b/hardhat.config.ts new file mode 100644 index 000000000..7519e7898 --- /dev/null +++ b/hardhat.config.ts @@ -0,0 +1,72 @@ +import '@typechain/hardhat'; +import '@nomiclabs/hardhat-waffle'; +import '@nomiclabs/hardhat-ethers'; +import '@axieinfinity/hardhat-deploy'; + +import * as dotenv from 'dotenv'; +import { HardhatUserConfig, NetworkUserConfig, SolcUserConfig } from 'hardhat/types'; + +dotenv.config(); + +const DEFAULT_MNEMONIC = 'title spike pink garlic hamster sorry few damage silver mushroom clever window'; + +const { TESTNET_PK, TESTNET_URL, MAINNET_PK, MAINNET_URL } = process.env; + +if (!TESTNET_PK) { + console.warn('TESTNET_PK is unset. Using DEFAULT_MNEMONIC'); +} + +if (!MAINNET_PK) { + console.warn('MAINNET_PK is unset. Using DEFAULT_MNEMONIC'); +} + +const testnet: NetworkUserConfig = { + chainId: 2021, + url: TESTNET_URL || 'https://testnet.skymavis.one/rpc', + accounts: TESTNET_PK ? [TESTNET_PK] : { mnemonic: DEFAULT_MNEMONIC }, + blockGasLimit: 100000000, +}; + +const mainnet: NetworkUserConfig = { + chainId: 2020, + url: MAINNET_URL || 'https://api.roninchain.com/rpc', + accounts: MAINNET_PK ? [MAINNET_PK] : { mnemonic: DEFAULT_MNEMONIC }, + blockGasLimit: 100000000, +}; + +const compilerConfig: SolcUserConfig = { + version: '0.8.9', + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, +}; + +const config: HardhatUserConfig = { + solidity: { + compilers: [compilerConfig, { ...compilerConfig, version: '0.5.17' }], + }, + typechain: { + outDir: 'src/types', + }, + paths: { + deploy: 'src/deploy', + }, + namedAccounts: { + deployer: 0, + trezor: 'trezor://0x0000000000000000000000000000000000000000', + }, + networks: { + hardhat: { + accounts: { + mnemonic: DEFAULT_MNEMONIC, + }, + }, + 'ronin-testnet': testnet, + 'ronin-mainnet': mainnet, + }, +}; + +export default config; diff --git a/package.json b/package.json new file mode 100644 index 000000000..876454f2d --- /dev/null +++ b/package.json @@ -0,0 +1,55 @@ +{ + "name": "ronin-dpos-contract", + "version": "1.0.0", + "description": "Axie Infinity Dapps smart contracts.", + "author": "Axie Infinity Engineering ", + "main": "dist/src/types/index.js", + "types": "dist/src/types/index.d.ts", + "scripts": { + "compile": "hardhat compile", + "test": "hardhat test", + "clean": "hardhat clean && rimraf dist cache", + "lint:fix": "lint-staged", + "docgen": "solidity-docgen -t templates/docs -H templates/docs/helper.js -i contracts -e contracts/mocks -o docs --solc-module solc-0.8", + "prepare": "husky install", + "build": "hardhat compile && tsc -p tsconfig.build.json" + }, + "lint-staged": { + "contracts/**/*.sol": "prettier --write", + "{test,src}/!(types)/*.{js,ts}": "prettier --write", + "src/*.{js,ts}": "prettier --write", + "hardhat.config.{js,ts}": "prettier --write" + }, + "dependencies": { + "@openzeppelin/contracts": "^4.6.0", + "dotenv": "^10.0.0" + }, + "devDependencies": { + "@nomiclabs/hardhat-ethers": "^2.0.3", + "@nomiclabs/hardhat-waffle": "^2.0.1", + "@typechain/ethers-v5": "^8.0.5", + "@typechain/hardhat": "^3.0.0", + "@types/chai": "^4.3.0", + "@types/mocha": "^9.0.0", + "@types/node": "^17.0.0", + "chai": "^4.3.4", + "ethereum-waffle": "^3.4.0", + "ethers": "^5.5.2", + "hardhat": "^2.7.1", + "hardhat-deploy": "^0.9.14", + "husky": "^7.0.4", + "lint-staged": ">=10", + "prettier": "^2.5.1", + "prettier-plugin-solidity": "^1.0.0-beta.19", + "rimraf": "^3.0.2", + "solc-0.8": "npm:solc@^0.8.0", + "solhint": "^3.3.6", + "solidity-docgen": "0.5.16", + "ts-node": "^10.4.0", + "typechain": "^6.0.5", + "typescript": "^4.5.4" + }, + "engines": { + "node": ">=12" + } +} diff --git a/pull_request_template.md b/pull_request_template.md new file mode 100644 index 000000000..d7b466d73 --- /dev/null +++ b/pull_request_template.md @@ -0,0 +1,6 @@ +### Description + +### Checklist +- [ ] I have clearly commented on all the main functions followed the [NatSpec Format](https://docs.soliditylang.org/en/v0.8.0/natspec-format.html) +- [ ] The box that allows repo maintainers to update this PR is checked +- [ ] I tested locally to make sure this feature/fix works diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 000000000..6c95f332d --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "downlevelIteration": true, + "lib": ["dom", "es2015"], + "module": "commonjs", + "noImplicitReturns": true, + "strict": true, + "target": "es5", + "outDir": "dist", + "rootDir": ".", + "composite": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "skipLibCheck": true + }, + "include": ["./src/types"], + "files": [] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..b7dd6d43c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "outDir": "dist", + "composite": true, + "skipLibCheck": true + }, + "include": ["./src", "./test"], + "files": ["./hardhat.config.ts"] +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 000000000..18f22af6c --- /dev/null +++ b/yarn.lock @@ -0,0 +1,9434 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/helper-validator-identifier@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" + integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@ensdomains/ens@^0.4.4": + version "0.4.5" + resolved "https://registry.yarnpkg.com/@ensdomains/ens/-/ens-0.4.5.tgz#e0aebc005afdc066447c6e22feb4eda89a5edbfc" + integrity sha512-JSvpj1iNMFjK6K+uVl4unqMoa9rf5jopb8cya5UGBWz23Nw8hSNT7efgUx4BTlAPAgpNlEioUfeTyQ6J9ZvTVw== + dependencies: + bluebird "^3.5.2" + eth-ens-namehash "^2.0.8" + solc "^0.4.20" + testrpc "0.0.1" + web3-utils "^1.0.0-beta.31" + +"@ensdomains/resolver@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@ensdomains/resolver/-/resolver-0.2.4.tgz#c10fe28bf5efbf49bff4666d909aed0265efbc89" + integrity sha512-bvaTH34PMCbv6anRa9I/0zjLJgY4EuznbEMgbV77JBCQ9KNC46rzi0avuxpOfu+xDjPEtSFGqVEOr5GlUSGudA== + +"@ethereum-waffle/chai@^3.4.4": + version "3.4.4" + resolved "https://registry.yarnpkg.com/@ethereum-waffle/chai/-/chai-3.4.4.tgz#16c4cc877df31b035d6d92486dfdf983df9138ff" + integrity sha512-/K8czydBtXXkcM9X6q29EqEkc5dN3oYenyH2a9hF7rGAApAJUpH8QBtojxOY/xQ2up5W332jqgxwp0yPiYug1g== + dependencies: + "@ethereum-waffle/provider" "^3.4.4" + ethers "^5.5.2" + +"@ethereum-waffle/compiler@^3.4.4": + version "3.4.4" + resolved "https://registry.yarnpkg.com/@ethereum-waffle/compiler/-/compiler-3.4.4.tgz#d568ee0f6029e68b5c645506079fbf67d0dfcf19" + integrity sha512-RUK3axJ8IkD5xpWjWoJgyHclOeEzDLQFga6gKpeGxiS/zBu+HB0W2FvsrrLalTFIaPw/CGYACRBSIxqiCqwqTQ== + dependencies: + "@resolver-engine/imports" "^0.3.3" + "@resolver-engine/imports-fs" "^0.3.3" + "@typechain/ethers-v5" "^2.0.0" + "@types/mkdirp" "^0.5.2" + "@types/node-fetch" "^2.5.5" + ethers "^5.0.1" + mkdirp "^0.5.1" + node-fetch "^2.6.1" + solc "^0.6.3" + ts-generator "^0.1.1" + typechain "^3.0.0" + +"@ethereum-waffle/ens@^3.4.4": + version "3.4.4" + resolved "https://registry.yarnpkg.com/@ethereum-waffle/ens/-/ens-3.4.4.tgz#db97ea2c9decbb70b9205d53de2ccbd6f3182ba1" + integrity sha512-0m4NdwWxliy3heBYva1Wr4WbJKLnwXizmy5FfSSr5PMbjI7SIGCdCB59U7/ZzY773/hY3bLnzLwvG5mggVjJWg== + dependencies: + "@ensdomains/ens" "^0.4.4" + "@ensdomains/resolver" "^0.2.4" + ethers "^5.5.2" + +"@ethereum-waffle/mock-contract@^3.4.4": + version "3.4.4" + resolved "https://registry.yarnpkg.com/@ethereum-waffle/mock-contract/-/mock-contract-3.4.4.tgz#fc6ffa18813546f4950a69f5892d4dd54b2c685a" + integrity sha512-Mp0iB2YNWYGUV+VMl5tjPsaXKbKo8MDH9wSJ702l9EBjdxFf/vBvnMBAC1Fub1lLtmD0JHtp1pq+mWzg/xlLnA== + dependencies: + "@ethersproject/abi" "^5.5.0" + ethers "^5.5.2" + +"@ethereum-waffle/provider@^3.4.4": + version "3.4.4" + resolved "https://registry.yarnpkg.com/@ethereum-waffle/provider/-/provider-3.4.4.tgz#398fc1f7eb91cc2df7d011272eacba8af0c7fffb" + integrity sha512-GK8oKJAM8+PKy2nK08yDgl4A80mFuI8zBkE0C9GqTRYQqvuxIyXoLmJ5NZU9lIwyWVv5/KsoA11BgAv2jXE82g== + dependencies: + "@ethereum-waffle/ens" "^3.4.4" + ethers "^5.5.2" + ganache-core "^2.13.2" + patch-package "^6.2.2" + postinstall-postinstall "^2.1.0" + +"@ethereumjs/block@^3.5.0", "@ethereumjs/block@^3.6.2", "@ethereumjs/block@^3.6.3": + version "3.6.3" + resolved "https://registry.yarnpkg.com/@ethereumjs/block/-/block-3.6.3.tgz#d96cbd7af38b92ebb3424223dbf773f5ccd27f84" + integrity sha512-CegDeryc2DVKnDkg5COQrE0bJfw/p0v3GBk2W5/Dj5dOVfEmb50Ux0GLnSPypooLnfqjwFaorGuT9FokWB3GRg== + dependencies: + "@ethereumjs/common" "^2.6.5" + "@ethereumjs/tx" "^3.5.2" + ethereumjs-util "^7.1.5" + merkle-patricia-tree "^4.2.4" + +"@ethereumjs/blockchain@^5.5.2", "@ethereumjs/blockchain@^5.5.3": + version "5.5.3" + resolved "https://registry.yarnpkg.com/@ethereumjs/blockchain/-/blockchain-5.5.3.tgz#aa49a6a04789da6b66b5bcbb0d0b98efc369f640" + integrity sha512-bi0wuNJ1gw4ByNCV56H0Z4Q7D+SxUbwyG12Wxzbvqc89PXLRNR20LBcSUZRKpN0+YCPo6m0XZL/JLio3B52LTw== + dependencies: + "@ethereumjs/block" "^3.6.2" + "@ethereumjs/common" "^2.6.4" + "@ethereumjs/ethash" "^1.1.0" + debug "^4.3.3" + ethereumjs-util "^7.1.5" + level-mem "^5.0.1" + lru-cache "^5.1.1" + semaphore-async-await "^1.5.1" + +"@ethereumjs/common@^2.6.4", "@ethereumjs/common@^2.6.5": + version "2.6.5" + resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.6.5.tgz#0a75a22a046272579d91919cb12d84f2756e8d30" + integrity sha512-lRyVQOeCDaIVtgfbowla32pzeDv2Obr8oR8Put5RdUBNRGr1VGPGQNGP6elWIpgK3YdpzqTOh4GyUGOureVeeA== + dependencies: + crc-32 "^1.2.0" + ethereumjs-util "^7.1.5" + +"@ethereumjs/ethash@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/ethash/-/ethash-1.1.0.tgz#7c5918ffcaa9cb9c1dc7d12f77ef038c11fb83fb" + integrity sha512-/U7UOKW6BzpA+Vt+kISAoeDie1vAvY4Zy2KF5JJb+So7+1yKmJeJEHOGSnQIj330e9Zyl3L5Nae6VZyh2TJnAA== + dependencies: + "@ethereumjs/block" "^3.5.0" + "@types/levelup" "^4.3.0" + buffer-xor "^2.0.1" + ethereumjs-util "^7.1.1" + miller-rabin "^4.0.0" + +"@ethereumjs/tx@^3.5.1", "@ethereumjs/tx@^3.5.2": + version "3.5.2" + resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.5.2.tgz#197b9b6299582ad84f9527ca961466fce2296c1c" + integrity sha512-gQDNJWKrSDGu2w7w0PzVXVBNMzb7wwdDOmOqczmhNjqFxFuIbhVJDwiGEnxFNC2/b8ifcZzY7MLcluizohRzNw== + dependencies: + "@ethereumjs/common" "^2.6.4" + ethereumjs-util "^7.1.5" + +"@ethereumjs/vm@^5.9.0": + version "5.9.3" + resolved "https://registry.yarnpkg.com/@ethereumjs/vm/-/vm-5.9.3.tgz#6d69202e4c132a4a1e1628ac246e92062e230823" + integrity sha512-Ha04TeF8goEglr8eL7hkkYyjhzdZS0PsoRURzYlTF6I0VVId5KjKb0N7MrA8GMgheN+UeTncfTgYx52D/WhEmg== + dependencies: + "@ethereumjs/block" "^3.6.3" + "@ethereumjs/blockchain" "^5.5.3" + "@ethereumjs/common" "^2.6.5" + "@ethereumjs/tx" "^3.5.2" + async-eventemitter "^0.2.4" + core-js-pure "^3.0.1" + debug "^4.3.3" + ethereumjs-util "^7.1.5" + functional-red-black-tree "^1.0.1" + mcl-wasm "^0.7.1" + merkle-patricia-tree "^4.2.4" + rustbn.js "~0.2.0" + +"@ethersproject/abi@5.0.0-beta.153": + version "5.0.0-beta.153" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.0-beta.153.tgz#43a37172b33794e4562999f6e2d555b7599a8eee" + integrity sha512-aXweZ1Z7vMNzJdLpR1CZUAIgnwjrZeUSvN9syCwlBaEBUFJmFY+HHnfuTI5vIhVs/mRkfJVrbEyl51JZQqyjAg== + dependencies: + "@ethersproject/address" ">=5.0.0-beta.128" + "@ethersproject/bignumber" ">=5.0.0-beta.130" + "@ethersproject/bytes" ">=5.0.0-beta.129" + "@ethersproject/constants" ">=5.0.0-beta.128" + "@ethersproject/hash" ">=5.0.0-beta.128" + "@ethersproject/keccak256" ">=5.0.0-beta.127" + "@ethersproject/logger" ">=5.0.0-beta.129" + "@ethersproject/properties" ">=5.0.0-beta.131" + "@ethersproject/strings" ">=5.0.0-beta.130" + +"@ethersproject/abi@5.6.4", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.0", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.6.3": + version "5.6.4" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.6.4.tgz#f6e01b6ed391a505932698ecc0d9e7a99ee60362" + integrity sha512-TTeZUlCeIHG6527/2goZA6gW5F8Emoc7MrZDC7hhP84aRGvW3TEdTnZR08Ls88YXM1m2SuK42Osw/jSi3uO8gg== + dependencies: + "@ethersproject/address" "^5.6.1" + "@ethersproject/bignumber" "^5.6.2" + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/constants" "^5.6.1" + "@ethersproject/hash" "^5.6.1" + "@ethersproject/keccak256" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/strings" "^5.6.1" + +"@ethersproject/abstract-provider@5.6.1", "@ethersproject/abstract-provider@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.6.1.tgz#02ddce150785caf0c77fe036a0ebfcee61878c59" + integrity sha512-BxlIgogYJtp1FS8Muvj8YfdClk3unZH0vRMVX791Z9INBNT/kuACZ9GzaY1Y4yFq+YSy6/w4gzj3HCRKrK9hsQ== + dependencies: + "@ethersproject/bignumber" "^5.6.2" + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/networks" "^5.6.3" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/transactions" "^5.6.2" + "@ethersproject/web" "^5.6.1" + +"@ethersproject/abstract-signer@5.6.2", "@ethersproject/abstract-signer@^5.4.1", "@ethersproject/abstract-signer@^5.6.2": + version "5.6.2" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.6.2.tgz#491f07fc2cbd5da258f46ec539664713950b0b33" + integrity sha512-n1r6lttFBG0t2vNiI3HoWaS/KdOt8xyDjzlP2cuevlWLG6EX0OwcKLyG/Kp/cuwNxdy/ous+R/DEMdTUwWQIjQ== + dependencies: + "@ethersproject/abstract-provider" "^5.6.1" + "@ethersproject/bignumber" "^5.6.2" + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + +"@ethersproject/address@5.6.1", "@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@^5.4.0", "@ethersproject/address@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.6.1.tgz#ab57818d9aefee919c5721d28cd31fd95eff413d" + integrity sha512-uOgF0kS5MJv9ZvCz7x6T2EXJSzotiybApn4XlOgoTX0xdtyVIJ7pF+6cGPxiEq/dpBiTfMiw7Yc81JcwhSYA0Q== + dependencies: + "@ethersproject/bignumber" "^5.6.2" + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/keccak256" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/rlp" "^5.6.1" + +"@ethersproject/base64@5.6.1", "@ethersproject/base64@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.6.1.tgz#2c40d8a0310c9d1606c2c37ae3092634b41d87cb" + integrity sha512-qB76rjop6a0RIYYMiB4Eh/8n+Hxu2NIZm8S/Q7kNo5pmZfXhHGHmS4MinUainiBC54SCyRnwzL+KZjj8zbsSsw== + dependencies: + "@ethersproject/bytes" "^5.6.1" + +"@ethersproject/basex@5.6.1", "@ethersproject/basex@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.6.1.tgz#badbb2f1d4a6f52ce41c9064f01eab19cc4c5305" + integrity sha512-a52MkVz4vuBXR06nvflPMotld1FJWSj2QT0985v7P/emPZO00PucFAkbcmq2vpVU7Ts7umKiSI6SppiLykVWsA== + dependencies: + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/properties" "^5.6.0" + +"@ethersproject/bignumber@5.6.2", "@ethersproject/bignumber@>=5.0.0-beta.130", "@ethersproject/bignumber@^5.4.1", "@ethersproject/bignumber@^5.6.2": + version "5.6.2" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.6.2.tgz#72a0717d6163fab44c47bcc82e0c550ac0315d66" + integrity sha512-v7+EEUbhGqT3XJ9LMPsKvXYHFc8eHxTowFCG/HgJErmq4XHJ2WR7aeyICg3uTOAQ7Icn0GFHAohXEhxQHq4Ubw== + dependencies: + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + bn.js "^5.2.1" + +"@ethersproject/bytes@5.6.1", "@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@^5.4.0", "@ethersproject/bytes@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.6.1.tgz#24f916e411f82a8a60412344bf4a813b917eefe7" + integrity sha512-NwQt7cKn5+ZE4uDn+X5RAXLp46E1chXoaMmrxAyA0rblpxz8t58lVkrHXoRIn0lz1joQElQ8410GqhTqMOwc6g== + dependencies: + "@ethersproject/logger" "^5.6.0" + +"@ethersproject/constants@5.6.1", "@ethersproject/constants@>=5.0.0-beta.128", "@ethersproject/constants@^5.4.0", "@ethersproject/constants@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.6.1.tgz#e2e974cac160dd101cf79fdf879d7d18e8cb1370" + integrity sha512-QSq9WVnZbxXYFftrjSjZDUshp6/eKp6qrtdBtUCm0QxCV5z1fG/w3kdlcsjMCQuQHUnAclKoK7XpXMezhRDOLg== + dependencies: + "@ethersproject/bignumber" "^5.6.2" + +"@ethersproject/contracts@5.6.2", "@ethersproject/contracts@^5.4.1": + version "5.6.2" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.6.2.tgz#20b52e69ebc1b74274ff8e3d4e508de971c287bc" + integrity sha512-hguUA57BIKi6WY0kHvZp6PwPlWF87MCeB4B7Z7AbUpTxfFXFdn/3b0GmjZPagIHS+3yhcBJDnuEfU4Xz+Ks/8g== + dependencies: + "@ethersproject/abi" "^5.6.3" + "@ethersproject/abstract-provider" "^5.6.1" + "@ethersproject/abstract-signer" "^5.6.2" + "@ethersproject/address" "^5.6.1" + "@ethersproject/bignumber" "^5.6.2" + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/constants" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/transactions" "^5.6.2" + +"@ethersproject/hash@5.6.1", "@ethersproject/hash@>=5.0.0-beta.128", "@ethersproject/hash@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.6.1.tgz#224572ea4de257f05b4abf8ae58b03a67e99b0f4" + integrity sha512-L1xAHurbaxG8VVul4ankNX5HgQ8PNCTrnVXEiFnE9xoRnaUcgfD12tZINtDinSllxPLCtGwguQxJ5E6keE84pA== + dependencies: + "@ethersproject/abstract-signer" "^5.6.2" + "@ethersproject/address" "^5.6.1" + "@ethersproject/bignumber" "^5.6.2" + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/keccak256" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/strings" "^5.6.1" + +"@ethersproject/hdnode@5.6.2", "@ethersproject/hdnode@^5.6.2": + version "5.6.2" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.6.2.tgz#26f3c83a3e8f1b7985c15d1db50dc2903418b2d2" + integrity sha512-tERxW8Ccf9CxW2db3WsN01Qao3wFeRsfYY9TCuhmG0xNpl2IO8wgXU3HtWIZ49gUWPggRy4Yg5axU0ACaEKf1Q== + dependencies: + "@ethersproject/abstract-signer" "^5.6.2" + "@ethersproject/basex" "^5.6.1" + "@ethersproject/bignumber" "^5.6.2" + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/pbkdf2" "^5.6.1" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/sha2" "^5.6.1" + "@ethersproject/signing-key" "^5.6.2" + "@ethersproject/strings" "^5.6.1" + "@ethersproject/transactions" "^5.6.2" + "@ethersproject/wordlists" "^5.6.1" + +"@ethersproject/json-wallets@5.6.1", "@ethersproject/json-wallets@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.6.1.tgz#3f06ba555c9c0d7da46756a12ac53483fe18dd91" + integrity sha512-KfyJ6Zwz3kGeX25nLihPwZYlDqamO6pfGKNnVMWWfEVVp42lTfCZVXXy5Ie8IZTN0HKwAngpIPi7gk4IJzgmqQ== + dependencies: + "@ethersproject/abstract-signer" "^5.6.2" + "@ethersproject/address" "^5.6.1" + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/hdnode" "^5.6.2" + "@ethersproject/keccak256" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/pbkdf2" "^5.6.1" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/random" "^5.6.1" + "@ethersproject/strings" "^5.6.1" + "@ethersproject/transactions" "^5.6.2" + aes-js "3.0.0" + scrypt-js "3.0.1" + +"@ethersproject/keccak256@5.6.1", "@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.6.1.tgz#b867167c9b50ba1b1a92bccdd4f2d6bd168a91cc" + integrity sha512-bB7DQHCTRDooZZdL3lk9wpL0+XuG3XLGHLh3cePnybsO3V0rdCAOQGpn/0R3aODmnTOOkCATJiD2hnL+5bwthA== + dependencies: + "@ethersproject/bytes" "^5.6.1" + js-sha3 "0.8.0" + +"@ethersproject/logger@5.6.0", "@ethersproject/logger@>=5.0.0-beta.129", "@ethersproject/logger@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.6.0.tgz#d7db1bfcc22fd2e4ab574cba0bb6ad779a9a3e7a" + integrity sha512-BiBWllUROH9w+P21RzoxJKzqoqpkyM1pRnEKG69bulE9TSQD8SAIvTQqIMZmmCO8pUNkgLP1wndX1gKghSpBmg== + +"@ethersproject/networks@5.6.4", "@ethersproject/networks@^5.6.3": + version "5.6.4" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.6.4.tgz#51296d8fec59e9627554f5a8a9c7791248c8dc07" + integrity sha512-KShHeHPahHI2UlWdtDMn2lJETcbtaJge4k7XSjDR9h79QTd6yQJmv6Cp2ZA4JdqWnhszAOLSuJEd9C0PRw7hSQ== + dependencies: + "@ethersproject/logger" "^5.6.0" + +"@ethersproject/pbkdf2@5.6.1", "@ethersproject/pbkdf2@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.6.1.tgz#f462fe320b22c0d6b1d72a9920a3963b09eb82d1" + integrity sha512-k4gRQ+D93zDRPNUfmduNKq065uadC2YjMP/CqwwX5qG6R05f47boq6pLZtV/RnC4NZAYOPH1Cyo54q0c9sshRQ== + dependencies: + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/sha2" "^5.6.1" + +"@ethersproject/properties@5.6.0", "@ethersproject/properties@>=5.0.0-beta.131", "@ethersproject/properties@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.6.0.tgz#38904651713bc6bdd5bdd1b0a4287ecda920fa04" + integrity sha512-szoOkHskajKePTJSZ46uHUWWkbv7TzP2ypdEK6jGMqJaEt2sb0jCgfBo0gH0m2HBpRixMuJ6TBRaQCF7a9DoCg== + dependencies: + "@ethersproject/logger" "^5.6.0" + +"@ethersproject/providers@5.6.8", "@ethersproject/providers@^5.4.4": + version "5.6.8" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.6.8.tgz#22e6c57be215ba5545d3a46cf759d265bb4e879d" + integrity sha512-Wf+CseT/iOJjrGtAOf3ck9zS7AgPmr2fZ3N97r4+YXN3mBePTG2/bJ8DApl9mVwYL+RpYbNxMEkEp4mPGdwG/w== + dependencies: + "@ethersproject/abstract-provider" "^5.6.1" + "@ethersproject/abstract-signer" "^5.6.2" + "@ethersproject/address" "^5.6.1" + "@ethersproject/base64" "^5.6.1" + "@ethersproject/basex" "^5.6.1" + "@ethersproject/bignumber" "^5.6.2" + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/constants" "^5.6.1" + "@ethersproject/hash" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/networks" "^5.6.3" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/random" "^5.6.1" + "@ethersproject/rlp" "^5.6.1" + "@ethersproject/sha2" "^5.6.1" + "@ethersproject/strings" "^5.6.1" + "@ethersproject/transactions" "^5.6.2" + "@ethersproject/web" "^5.6.1" + bech32 "1.1.4" + ws "7.4.6" + +"@ethersproject/random@5.6.1", "@ethersproject/random@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.6.1.tgz#66915943981bcd3e11bbd43733f5c3ba5a790255" + integrity sha512-/wtPNHwbmng+5yi3fkipA8YBT59DdkGRoC2vWk09Dci/q5DlgnMkhIycjHlavrvrjJBkFjO/ueLyT+aUDfc4lA== + dependencies: + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + +"@ethersproject/rlp@5.6.1", "@ethersproject/rlp@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.6.1.tgz#df8311e6f9f24dcb03d59a2bac457a28a4fe2bd8" + integrity sha512-uYjmcZx+DKlFUk7a5/W9aQVaoEC7+1MOBgNtvNg13+RnuUwT4F0zTovC0tmay5SmRslb29V1B7Y5KCri46WhuQ== + dependencies: + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + +"@ethersproject/sha2@5.6.1", "@ethersproject/sha2@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.6.1.tgz#211f14d3f5da5301c8972a8827770b6fd3e51656" + integrity sha512-5K2GyqcW7G4Yo3uenHegbXRPDgARpWUiXc6RiF7b6i/HXUoWlb7uCARh7BAHg7/qT/Q5ydofNwiZcim9qpjB6g== + dependencies: + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + hash.js "1.1.7" + +"@ethersproject/signing-key@5.6.2", "@ethersproject/signing-key@^5.6.2": + version "5.6.2" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.6.2.tgz#8a51b111e4d62e5a62aee1da1e088d12de0614a3" + integrity sha512-jVbu0RuP7EFpw82vHcL+GP35+KaNruVAZM90GxgQnGqB6crhBqW/ozBfFvdeImtmb4qPko0uxXjn8l9jpn0cwQ== + dependencies: + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + bn.js "^5.2.1" + elliptic "6.5.4" + hash.js "1.1.7" + +"@ethersproject/solidity@5.6.1", "@ethersproject/solidity@^5.4.0": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.6.1.tgz#5845e71182c66d32e6ec5eefd041fca091a473e2" + integrity sha512-KWqVLkUUoLBfL1iwdzUVlkNqAUIFMpbbeH0rgCfKmJp0vFtY4AsaN91gHKo9ZZLkC4UOm3cI3BmMV4N53BOq4g== + dependencies: + "@ethersproject/bignumber" "^5.6.2" + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/keccak256" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/sha2" "^5.6.1" + "@ethersproject/strings" "^5.6.1" + +"@ethersproject/strings@5.6.1", "@ethersproject/strings@>=5.0.0-beta.130", "@ethersproject/strings@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.6.1.tgz#dbc1b7f901db822b5cafd4ebf01ca93c373f8952" + integrity sha512-2X1Lgk6Jyfg26MUnsHiT456U9ijxKUybz8IM1Vih+NJxYtXhmvKBcHOmvGqpFSVJ0nQ4ZCoIViR8XlRw1v/+Cw== + dependencies: + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/constants" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + +"@ethersproject/transactions@5.6.2", "@ethersproject/transactions@^5.0.0-beta.135", "@ethersproject/transactions@^5.4.0", "@ethersproject/transactions@^5.6.2": + version "5.6.2" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.6.2.tgz#793a774c01ced9fe7073985bb95a4b4e57a6370b" + integrity sha512-BuV63IRPHmJvthNkkt9G70Ullx6AcM+SDc+a8Aw/8Yew6YwT51TcBKEp1P4oOQ/bP25I18JJr7rcFRgFtU9B2Q== + dependencies: + "@ethersproject/address" "^5.6.1" + "@ethersproject/bignumber" "^5.6.2" + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/constants" "^5.6.1" + "@ethersproject/keccak256" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/rlp" "^5.6.1" + "@ethersproject/signing-key" "^5.6.2" + +"@ethersproject/units@5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.6.1.tgz#ecc590d16d37c8f9ef4e89e2005bda7ddc6a4e6f" + integrity sha512-rEfSEvMQ7obcx3KWD5EWWx77gqv54K6BKiZzKxkQJqtpriVsICrktIQmKl8ReNToPeIYPnFHpXvKpi068YFZXw== + dependencies: + "@ethersproject/bignumber" "^5.6.2" + "@ethersproject/constants" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + +"@ethersproject/wallet@5.6.2", "@ethersproject/wallet@^5.4.0": + version "5.6.2" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.6.2.tgz#cd61429d1e934681e413f4bc847a5f2f87e3a03c" + integrity sha512-lrgh0FDQPuOnHcF80Q3gHYsSUODp6aJLAdDmDV0xKCN/T7D99ta1jGVhulg3PY8wiXEngD0DfM0I2XKXlrqJfg== + dependencies: + "@ethersproject/abstract-provider" "^5.6.1" + "@ethersproject/abstract-signer" "^5.6.2" + "@ethersproject/address" "^5.6.1" + "@ethersproject/bignumber" "^5.6.2" + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/hash" "^5.6.1" + "@ethersproject/hdnode" "^5.6.2" + "@ethersproject/json-wallets" "^5.6.1" + "@ethersproject/keccak256" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/random" "^5.6.1" + "@ethersproject/signing-key" "^5.6.2" + "@ethersproject/transactions" "^5.6.2" + "@ethersproject/wordlists" "^5.6.1" + +"@ethersproject/web@5.6.1", "@ethersproject/web@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.6.1.tgz#6e2bd3ebadd033e6fe57d072db2b69ad2c9bdf5d" + integrity sha512-/vSyzaQlNXkO1WV+RneYKqCJwualcUdx/Z3gseVovZP0wIlOFcCE1hkRhKBH8ImKbGQbMl9EAAyJFrJu7V0aqA== + dependencies: + "@ethersproject/base64" "^5.6.1" + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/strings" "^5.6.1" + +"@ethersproject/wordlists@5.6.1", "@ethersproject/wordlists@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.6.1.tgz#1e78e2740a8a21e9e99947e47979d72e130aeda1" + integrity sha512-wiPRgBpNbNwCQFoCr8bcWO8o5I810cqO6mkdtKfLKFlLxeCWcnzDi4Alu8iyNzlhYuS9npCwivMbRWF19dyblw== + dependencies: + "@ethersproject/bytes" "^5.6.1" + "@ethersproject/hash" "^5.6.1" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/strings" "^5.6.1" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@metamask/eth-sig-util@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz#3ad61f6ea9ad73ba5b19db780d40d9aae5157088" + integrity sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ== + dependencies: + ethereumjs-abi "^0.6.8" + ethereumjs-util "^6.2.1" + ethjs-util "^0.1.6" + tweetnacl "^1.0.3" + tweetnacl-util "^0.15.1" + +"@noble/hashes@1.1.2", "@noble/hashes@~1.1.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.2.tgz#e9e035b9b166ca0af657a7848eb2718f0f22f183" + integrity sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA== + +"@noble/secp256k1@1.6.3", "@noble/secp256k1@~1.6.0": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.6.3.tgz#7eed12d9f4404b416999d0c87686836c4c5c9b94" + integrity sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@nomiclabs/hardhat-ethers@^2.0.3": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.1.1.tgz#3f1d1ab49813d1bae4c035cc1adec224711e528b" + integrity sha512-Gg0IFkT/DW3vOpih4/kMjeZCLYqtfgECLeLXTs7ZDPzcK0cfoc5wKk4nq5n/izCUzdhidO/Utd6ptF9JrWwWVA== + +"@nomiclabs/hardhat-waffle@^2.0.1": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-waffle/-/hardhat-waffle-2.0.3.tgz#9c538a09c5ed89f68f5fd2dc3f78f16ed1d6e0b1" + integrity sha512-049PHSnI1CZq6+XTbrMbMv5NaL7cednTfPenx02k3cEh8wBMLa6ys++dBETJa6JjfwgA9nBhhHQ173LJv6k2Pg== + dependencies: + "@types/sinon-chai" "^3.2.3" + "@types/web3" "1.0.19" + +"@oclif/command@^1.8.0", "@oclif/command@^1.8.15": + version "1.8.16" + resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.8.16.tgz#bea46f81b2061b47e1cda318a0b923e62ca4cc0c" + integrity sha512-rmVKYEsKzurfRU0xJz+iHelbi1LGlihIWZ7Qvmb/CBz1EkhL7nOkW4SVXmG2dA5Ce0si2gr88i6q4eBOMRNJ1w== + dependencies: + "@oclif/config" "^1.18.2" + "@oclif/errors" "^1.3.5" + "@oclif/help" "^1.0.1" + "@oclif/parser" "^3.8.6" + debug "^4.1.1" + semver "^7.3.2" + +"@oclif/config@1.18.2": + version "1.18.2" + resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.18.2.tgz#5bfe74a9ba6a8ca3dceb314a81bd9ce2e15ebbfe" + integrity sha512-cE3qfHWv8hGRCP31j7fIS7BfCflm/BNZ2HNqHexH+fDrdF2f1D5S8VmXWLC77ffv3oDvWyvE9AZeR0RfmHCCaA== + dependencies: + "@oclif/errors" "^1.3.3" + "@oclif/parser" "^3.8.0" + debug "^4.1.1" + globby "^11.0.1" + is-wsl "^2.1.1" + tslib "^2.0.0" + +"@oclif/config@^1.17.0", "@oclif/config@^1.18.2": + version "1.18.3" + resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.18.3.tgz#ddfc144fdab66b1658c2f1b3478fa7fbfd317e79" + integrity sha512-sBpko86IrTscc39EvHUhL+c++81BVTsIZ3ETu/vG+cCdi0N6vb2DoahR67A9FI2CGnxRRHjnTfa3m6LulwNATA== + dependencies: + "@oclif/errors" "^1.3.5" + "@oclif/parser" "^3.8.0" + debug "^4.1.1" + globby "^11.0.1" + is-wsl "^2.1.1" + tslib "^2.3.1" + +"@oclif/errors@1.3.5", "@oclif/errors@^1.3.3", "@oclif/errors@^1.3.5": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@oclif/errors/-/errors-1.3.5.tgz#a1e9694dbeccab10fe2fe15acb7113991bed636c" + integrity sha512-OivucXPH/eLLlOT7FkCMoZXiaVYf8I/w1eTAM1+gKzfhALwWTusxEx7wBmW0uzvkSg/9ovWLycPaBgJbM3LOCQ== + dependencies: + clean-stack "^3.0.0" + fs-extra "^8.1" + indent-string "^4.0.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +"@oclif/help@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@oclif/help/-/help-1.0.1.tgz#fd96a3dd9fb2314479e6c8584c91b63754a7dff5" + integrity sha512-8rsl4RHL5+vBUAKBL6PFI3mj58hjPCp2VYyXD4TAa7IMStikFfOH2gtWmqLzIlxAED2EpD0dfYwo9JJxYsH7Aw== + dependencies: + "@oclif/config" "1.18.2" + "@oclif/errors" "1.3.5" + chalk "^4.1.2" + indent-string "^4.0.0" + lodash "^4.17.21" + string-width "^4.2.0" + strip-ansi "^6.0.0" + widest-line "^3.1.0" + wrap-ansi "^6.2.0" + +"@oclif/linewrap@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@oclif/linewrap/-/linewrap-1.0.0.tgz#aedcb64b479d4db7be24196384897b5000901d91" + integrity sha512-Ups2dShK52xXa8w6iBWLgcjPJWjais6KPJQq3gQ/88AY6BXoTX+MIGFPrWQO1KLMiQfoTpcLnUwloN4brrVUHw== + +"@oclif/parser@^3.8.0", "@oclif/parser@^3.8.6": + version "3.8.7" + resolved "https://registry.yarnpkg.com/@oclif/parser/-/parser-3.8.7.tgz#236d48db05d0b00157d3b42d31f9dac7550d2a7c" + integrity sha512-b11xBmIUK+LuuwVGJpFs4LwQN2xj2cBWj2c4z1FtiXGrJ85h9xV6q+k136Hw0tGg1jQoRXuvuBnqQ7es7vO9/Q== + dependencies: + "@oclif/errors" "^1.3.5" + "@oclif/linewrap" "^1.0.0" + chalk "^4.1.0" + tslib "^2.3.1" + +"@oclif/plugin-help@^3.2.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@oclif/plugin-help/-/plugin-help-3.3.1.tgz#36adb4e0173f741df409bb4b69036d24a53bfb24" + integrity sha512-QuSiseNRJygaqAdABYFWn/H1CwIZCp9zp/PLid6yXvy6VcQV7OenEFF5XuYaCvSARe2Tg9r8Jqls5+fw1A9CbQ== + dependencies: + "@oclif/command" "^1.8.15" + "@oclif/config" "1.18.2" + "@oclif/errors" "1.3.5" + "@oclif/help" "^1.0.1" + chalk "^4.1.2" + indent-string "^4.0.0" + lodash "^4.17.21" + string-width "^4.2.0" + strip-ansi "^6.0.0" + widest-line "^3.1.0" + wrap-ansi "^6.2.0" + +"@openzeppelin/contracts@^4.6.0": + version "4.7.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.3.tgz#939534757a81f8d69cc854c7692805684ff3111e" + integrity sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw== + +"@resolver-engine/core@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@resolver-engine/core/-/core-0.3.3.tgz#590f77d85d45bc7ecc4e06c654f41345db6ca967" + integrity sha512-eB8nEbKDJJBi5p5SrvrvILn4a0h42bKtbCTri3ZxCGt6UvoQyp7HnGOfki944bUjBSHKK3RvgfViHn+kqdXtnQ== + dependencies: + debug "^3.1.0" + is-url "^1.2.4" + request "^2.85.0" + +"@resolver-engine/fs@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@resolver-engine/fs/-/fs-0.3.3.tgz#fbf83fa0c4f60154a82c817d2fe3f3b0c049a973" + integrity sha512-wQ9RhPUcny02Wm0IuJwYMyAG8fXVeKdmhm8xizNByD4ryZlx6PP6kRen+t/haF43cMfmaV7T3Cx6ChOdHEhFUQ== + dependencies: + "@resolver-engine/core" "^0.3.3" + debug "^3.1.0" + +"@resolver-engine/imports-fs@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@resolver-engine/imports-fs/-/imports-fs-0.3.3.tgz#4085db4b8d3c03feb7a425fbfcf5325c0d1e6c1b" + integrity sha512-7Pjg/ZAZtxpeyCFlZR5zqYkz+Wdo84ugB5LApwriT8XFeQoLwGUj4tZFFvvCuxaNCcqZzCYbonJgmGObYBzyCA== + dependencies: + "@resolver-engine/fs" "^0.3.3" + "@resolver-engine/imports" "^0.3.3" + debug "^3.1.0" + +"@resolver-engine/imports@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@resolver-engine/imports/-/imports-0.3.3.tgz#badfb513bb3ff3c1ee9fd56073e3144245588bcc" + integrity sha512-anHpS4wN4sRMwsAbMXhMfOD/y4a4Oo0Cw/5+rue7hSwGWsDOQaAU1ClK1OxjUC35/peazxEl8JaSRRS+Xb8t3Q== + dependencies: + "@resolver-engine/core" "^0.3.3" + debug "^3.1.0" + hosted-git-info "^2.6.0" + path-browserify "^1.0.0" + url "^0.11.0" + +"@scure/base@~1.1.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" + integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== + +"@scure/bip32@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.1.0.tgz#dea45875e7fbc720c2b4560325f1cf5d2246d95b" + integrity sha512-ftTW3kKX54YXLCxH6BB7oEEoJfoE2pIgw7MINKAs5PsS6nqKPuKk1haTF/EuHmYqG330t5GSrdmtRuHaY1a62Q== + dependencies: + "@noble/hashes" "~1.1.1" + "@noble/secp256k1" "~1.6.0" + "@scure/base" "~1.1.0" + +"@scure/bip39@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.0.tgz#92f11d095bae025f166bef3defcc5bf4945d419a" + integrity sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w== + dependencies: + "@noble/hashes" "~1.1.1" + "@scure/base" "~1.1.0" + +"@sentry/core@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" + integrity sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg== + dependencies: + "@sentry/hub" "5.30.0" + "@sentry/minimal" "5.30.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" + tslib "^1.9.3" + +"@sentry/hub@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.30.0.tgz#2453be9b9cb903404366e198bd30c7ca74cdc100" + integrity sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ== + dependencies: + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" + tslib "^1.9.3" + +"@sentry/minimal@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.30.0.tgz#ce3d3a6a273428e0084adcb800bc12e72d34637b" + integrity sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw== + dependencies: + "@sentry/hub" "5.30.0" + "@sentry/types" "5.30.0" + tslib "^1.9.3" + +"@sentry/node@^5.18.1": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.30.0.tgz#4ca479e799b1021285d7fe12ac0858951c11cd48" + integrity sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg== + dependencies: + "@sentry/core" "5.30.0" + "@sentry/hub" "5.30.0" + "@sentry/tracing" "5.30.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" + cookie "^0.4.1" + https-proxy-agent "^5.0.0" + lru_map "^0.3.3" + tslib "^1.9.3" + +"@sentry/tracing@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.30.0.tgz#501d21f00c3f3be7f7635d8710da70d9419d4e1f" + integrity sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw== + dependencies: + "@sentry/hub" "5.30.0" + "@sentry/minimal" "5.30.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" + tslib "^1.9.3" + +"@sentry/types@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.30.0.tgz#19709bbe12a1a0115bc790b8942917da5636f402" + integrity sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw== + +"@sentry/utils@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.30.0.tgz#9a5bd7ccff85ccfe7856d493bffa64cabc41e980" + integrity sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww== + dependencies: + "@sentry/types" "5.30.0" + tslib "^1.9.3" + +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + +"@solidity-parser/parser@^0.14.1", "@solidity-parser/parser@^0.14.2", "@solidity-parser/parser@^0.14.3": + version "0.14.3" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.3.tgz#0d627427b35a40d8521aaa933cc3df7d07bfa36f" + integrity sha512-29g2SZ29HtsqA58pLCtopI1P/cPy5/UAzlcAXO6T/CNJimG6yA8kx4NaseMyJULiC+TEs02Y9/yeHzClqoA0hw== + dependencies: + antlr4ts "^0.5.0-alpha.4" + +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + +"@typechain/ethers-v5@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-2.0.0.tgz#cd3ca1590240d587ca301f4c029b67bfccd08810" + integrity sha512-0xdCkyGOzdqh4h5JSf+zoWx85IusEjDcPIwNEHP8mrWSnCae4rvrqB+/gtpdNfX7zjlFlZiMeePn2r63EI3Lrw== + dependencies: + ethers "^5.0.2" + +"@typechain/ethers-v5@^8.0.5": + version "8.0.5" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-8.0.5.tgz#d469420e9a73deb7fa076cde9edb45d713dd1b8c" + integrity sha512-ntpj4cS3v4WlDu+hSKSyj9A3o1tKtWC30RX1gobeYymZColeJiUemC1Kgfa0MWGmInm5CKxoHVhEvYVgPOZn1A== + dependencies: + lodash "^4.17.15" + ts-essentials "^7.0.1" + +"@typechain/hardhat@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-3.1.0.tgz#88bd9e9d55e30fbece6fbb34c03ecd40a8b2013a" + integrity sha512-C6Be6l+vTpao19PvMH2CB/lhL1TRLkhdPkvQCF/zqkY1e+0iqY2Bb9Jd3PTt6I8QvMm89ZDerrCJC9927ZHmlg== + dependencies: + fs-extra "^9.1.0" + +"@types/abstract-leveldown@*": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@types/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz#f055979a99f7654e84d6b8e6267419e9c4cfff87" + integrity sha512-q5veSX6zjUy/DlDhR4Y4cU0k2Ar+DT2LUraP00T19WLmTO6Se1djepCCaqU6nQrwcJ5Hyo/CWqxTzrrFg8eqbQ== + +"@types/bn.js@*", "@types/bn.js@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.0.tgz#32c5d271503a12653c62cf4d2b45e6eab8cebc68" + integrity sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA== + dependencies: + "@types/node" "*" + +"@types/bn.js@^4.11.3", "@types/bn.js@^4.11.5": + version "4.11.6" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" + integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg== + dependencies: + "@types/node" "*" + +"@types/chai@*", "@types/chai@^4.3.0": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.3.tgz#3c90752792660c4b562ad73b3fbd68bf3bc7ae07" + integrity sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g== + +"@types/level-errors@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/level-errors/-/level-errors-3.0.0.tgz#15c1f4915a5ef763b51651b15e90f6dc081b96a8" + integrity sha512-/lMtoq/Cf/2DVOm6zE6ORyOM+3ZVm/BvzEZVxUhf6bgh8ZHglXlBqxbxSlJeVp8FCbD3IVvk/VbsaNmDjrQvqQ== + +"@types/levelup@^4.3.0": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@types/levelup/-/levelup-4.3.3.tgz#4dc2b77db079b1cf855562ad52321aa4241b8ef4" + integrity sha512-K+OTIjJcZHVlZQN1HmU64VtrC0jC3dXWQozuEIR9zVvltIk90zaGPM2AgT+fIkChpzHhFE3YnvFLCbLtzAmexA== + dependencies: + "@types/abstract-leveldown" "*" + "@types/level-errors" "*" + "@types/node" "*" + +"@types/lru-cache@^5.1.0": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.1.tgz#c48c2e27b65d2a153b19bfc1a317e30872e01eef" + integrity sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw== + +"@types/mkdirp@^0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f" + integrity sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg== + dependencies: + "@types/node" "*" + +"@types/mocha@^9.0.0": + version "9.1.1" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" + integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== + +"@types/node-fetch@^2.5.5": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" + integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A== + dependencies: + "@types/node" "*" + form-data "^3.0.0" + +"@types/node@*": + version "18.7.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.6.tgz#31743bc5772b6ac223845e18c3fc26f042713c83" + integrity sha512-EdxgKRXgYsNITy5mjjXjVE/CS8YENSdhiagGrLqjG0pvA2owgJ6i4l7wy/PFZGC0B1/H20lWKN7ONVDNYDZm7A== + +"@types/node@^12.12.6": + version "12.20.55" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" + integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== + +"@types/node@^17.0.0": + version "17.0.45" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" + integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== + +"@types/pbkdf2@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.0.tgz#039a0e9b67da0cdc4ee5dab865caa6b267bb66b1" + integrity sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ== + dependencies: + "@types/node" "*" + +"@types/prettier@^2.1.1": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.0.tgz#ea03e9f0376a4446f44797ca19d9c46c36e352dc" + integrity sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A== + +"@types/qs@^6.9.7": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/resolve@^0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" + integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== + dependencies: + "@types/node" "*" + +"@types/secp256k1@^4.0.1": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.3.tgz#1b8e55d8e00f08ee7220b4d59a6abe89c37a901c" + integrity sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w== + dependencies: + "@types/node" "*" + +"@types/sinon-chai@^3.2.3": + version "3.2.8" + resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-3.2.8.tgz#5871d09ab50d671d8e6dd72e9073f8e738ac61dc" + integrity sha512-d4ImIQbT/rKMG8+AXpmcan5T2/PNeSjrYhvkwet6z0p8kzYtfgA32xzOBlbU0yqJfq+/0Ml805iFoODO0LP5/g== + dependencies: + "@types/chai" "*" + "@types/sinon" "*" + +"@types/sinon@*": + version "10.0.13" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.13.tgz#60a7a87a70d9372d0b7b38cc03e825f46981fb83" + integrity sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ== + dependencies: + "@types/sinonjs__fake-timers" "*" + +"@types/sinonjs__fake-timers@*": + version "8.1.2" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz#bf2e02a3dbd4aecaf95942ecd99b7402e03fad5e" + integrity sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA== + +"@types/underscore@*": + version "1.11.4" + resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.11.4.tgz#62e393f8bc4bd8a06154d110c7d042a93751def3" + integrity sha512-uO4CD2ELOjw8tasUrAhvnn2W4A0ZECOvMjCivJr4gA9pGgjv+qxKWY9GLTMVEK8ej85BxQOocUyE7hImmSQYcg== + +"@types/web3@1.0.19": + version "1.0.19" + resolved "https://registry.yarnpkg.com/@types/web3/-/web3-1.0.19.tgz#46b85d91d398ded9ab7c85a5dd57cb33ac558924" + integrity sha512-fhZ9DyvDYDwHZUp5/STa9XW2re0E8GxoioYJ4pEUZ13YHpApSagixj7IAdoYH5uAK+UalGq6Ml8LYzmgRA/q+A== + dependencies: + "@types/bn.js" "*" + "@types/underscore" "*" + +"@ungap/promise-all-settled@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" + integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== + +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +abstract-leveldown@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-3.0.0.tgz#5cb89f958a44f526779d740d1440e743e0c30a57" + integrity sha512-KUWx9UWGQD12zsmLNj64/pndaz4iJh/Pj7nopgkfDG6RlCcbMZvT6+9l7dchK4idog2Is8VdC/PvNbFuFmalIQ== + dependencies: + xtend "~4.0.0" + +abstract-leveldown@^2.4.1, abstract-leveldown@~2.7.1: + version "2.7.2" + resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-2.7.2.tgz#87a44d7ebebc341d59665204834c8b7e0932cc93" + integrity sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w== + dependencies: + xtend "~4.0.0" + +abstract-leveldown@^5.0.0, abstract-leveldown@~5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-5.0.0.tgz#f7128e1f86ccabf7d2893077ce5d06d798e386c6" + integrity sha512-5mU5P1gXtsMIXg65/rsYGsi93+MlogXZ9FA8JnwKurHQg64bfXwGYVdVdijNTVNOlAsuIiOwHdvFFD5JqCJQ7A== + dependencies: + xtend "~4.0.0" + +abstract-leveldown@^6.2.1: + version "6.3.0" + resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-6.3.0.tgz#d25221d1e6612f820c35963ba4bd739928f6026a" + integrity sha512-TU5nlYgta8YrBMNpc9FwQzRbiXsj49gsALsXadbGHt9CROPzX5fB0rWDR5mtdpOOKa5XqRFpbj1QroPAoPzVjQ== + dependencies: + buffer "^5.5.0" + immediate "^3.2.3" + level-concat-iterator "~2.0.0" + level-supports "~1.0.0" + xtend "~4.0.0" + +abstract-leveldown@~2.6.0: + version "2.6.3" + resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz#1c5e8c6a5ef965ae8c35dfb3a8770c476b82c4b8" + integrity sha512-2++wDf/DYqkPR3o5tbfdhF96EfMApo1GpPfzOsR/ZYXdkSmELlvOOEAl9iKkRsktMPHdGjO4rtkBpf2I7TiTeA== + dependencies: + xtend "~4.0.0" + +abstract-leveldown@~6.2.1: + version "6.2.3" + resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz#036543d87e3710f2528e47040bc3261b77a9a8eb" + integrity sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ== + dependencies: + buffer "^5.5.0" + immediate "^3.2.3" + level-concat-iterator "~2.0.0" + level-supports "~1.0.0" + xtend "~4.0.0" + +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-jsx@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^6.0.7: + version "6.4.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" + integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== + +acorn@^8.4.1: + version "8.8.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + +adm-zip@^0.4.16: + version "0.4.16" + resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" + integrity sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg== + +aes-js@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" + integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== + +aes-js@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a" + integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ== + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv@^6.10.2, ajv@^6.12.3, ajv@^6.6.1, ajv@^6.9.1: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-colors@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-colors@^4.1.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +ansi-escapes@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + +ansi-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== + +ansi-regex@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" + integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== + +ansi-regex@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3" + integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ== + +antlr4@4.7.1: + version "4.7.1" + resolved "https://registry.yarnpkg.com/antlr4/-/antlr4-4.7.1.tgz#69984014f096e9e775f53dd9744bf994d8959773" + integrity sha512-haHyTW7Y9joE5MVs37P2lNYfU2RWBLfcRDD8OWldcdZm5TiCE91B5Xl1oWSwiDUSd4rlExpt2pu1fksYQjRBYQ== + +antlr4ts@^0.5.0-alpha.4: + version "0.5.0-alpha.4" + resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a" + integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ== + +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== + +array-back@^1.0.3, array-back@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-1.0.4.tgz#644ba7f095f7ffcf7c43b5f0dc39d3c1f03c063b" + integrity sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw== + dependencies: + typical "^2.6.0" + +array-back@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-2.0.0.tgz#6877471d51ecc9c9bfa6136fb6c7d5fe69748022" + integrity sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw== + dependencies: + typical "^2.6.1" + +array-back@^3.0.1, array-back@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" + integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== + +array-back@^4.0.1, array-back@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" + integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== + +array.prototype.reduce@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz#8167e80089f78bff70a99e20bd4201d4663b0a6f" + integrity sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.2" + es-array-method-boxes-properly "^1.0.0" + is-string "^1.0.7" + +asn1.js@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== + +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== + +ast-parents@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/ast-parents/-/ast-parents-0.0.1.tgz#508fd0f05d0c48775d9eccda2e174423261e8dd3" + integrity sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA== + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async-eventemitter@^0.2.2, async-eventemitter@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/async-eventemitter/-/async-eventemitter-0.2.4.tgz#f5e7c8ca7d3e46aab9ec40a292baf686a0bafaca" + integrity sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw== + dependencies: + async "^2.4.0" + +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +async@2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" + integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg== + dependencies: + lodash "^4.17.11" + +async@^1.4.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + integrity sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w== + +async@^2.0.1, async@^2.1.2, async@^2.4.0, async@^2.5.0, async@^2.6.1: + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== + dependencies: + lodash "^4.17.14" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== + +aws4@^1.8.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== + +axios@^0.21.1: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== + dependencies: + follow-redirects "^1.14.0" + +babel-code-frame@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + integrity sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g== + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +babel-core@^6.0.14, babel-core@^6.26.0: + version "6.26.3" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" + integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA== + dependencies: + babel-code-frame "^6.26.0" + babel-generator "^6.26.0" + babel-helpers "^6.24.1" + babel-messages "^6.23.0" + babel-register "^6.26.0" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + convert-source-map "^1.5.1" + debug "^2.6.9" + json5 "^0.5.1" + lodash "^4.17.4" + minimatch "^3.0.4" + path-is-absolute "^1.0.1" + private "^0.1.8" + slash "^1.0.0" + source-map "^0.5.7" + +babel-generator@^6.26.0: + version "6.26.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" + integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.17.4" + source-map "^0.5.7" + trim-right "^1.0.1" + +babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" + integrity sha512-gCtfYORSG1fUMX4kKraymq607FWgMWg+j42IFPc18kFQEsmtaibP4UrqsXt8FlEJle25HUd4tsoDR7H2wDhe9Q== + dependencies: + babel-helper-explode-assignable-expression "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-call-delegate@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" + integrity sha512-RL8n2NiEj+kKztlrVJM9JT1cXzzAdvWFh76xh/H1I4nKwunzE4INBXn8ieCZ+wh4zWszZk7NBS1s/8HR5jDkzQ== + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-define-map@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" + integrity sha512-bHkmjcC9lM1kmZcVpA5t2om2nzT/xiZpo6TJq7UlZ3wqKfzia4veeXbIhKvJXAMzhhEBd3cR1IElL5AenWEUpA== + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-helper-explode-assignable-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" + integrity sha512-qe5csbhbvq6ccry9G7tkXbzNtcDiH4r51rrPUbwwoTzZ18AqxWYRZT6AOmxrpxKnQBW0pYlBI/8vh73Z//78nQ== + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-function-name@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" + integrity sha512-Oo6+e2iX+o9eVvJ9Y5eKL5iryeRdsIkwRYheCuhYdVHsdEQysbc2z2QkqCLIYnNxkT5Ss3ggrHdXiDI7Dhrn4Q== + dependencies: + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-get-function-arity@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" + integrity sha512-WfgKFX6swFB1jS2vo+DwivRN4NB8XUdM3ij0Y1gnC21y1tdBoe6xjVnd7NSI6alv+gZXCtJqvrTeMW3fR/c0ng== + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-hoist-variables@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" + integrity sha512-zAYl3tqerLItvG5cKYw7f1SpvIxS9zi7ohyGHaI9cgDUjAT6YcY9jIEH5CstetP5wHIVSceXwNS7Z5BpJg+rOw== + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-optimise-call-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" + integrity sha512-Op9IhEaxhbRT8MDXx2iNuMgciu2V8lDvYCNQbDGjdBNCjaMvyLf4wl4A3b8IgndCyQF8TwfgsQ8T3VD8aX1/pA== + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-regex@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" + integrity sha512-VlPiWmqmGJp0x0oK27Out1D+71nVVCTSdlbhIVoaBAj2lUgrNjBCRR9+llO4lTSb2O4r7PJg+RobRkhBrf6ofg== + dependencies: + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-helper-remap-async-to-generator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" + integrity sha512-RYqaPD0mQyQIFRu7Ho5wE2yvA/5jxqCIj/Lv4BXNq23mHYu/vxikOy2JueLiBxQknwapwrJeNCesvY0ZcfnlHg== + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-replace-supers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" + integrity sha512-sLI+u7sXJh6+ToqDr57Bv973kCepItDhMou0xCP2YPVmR1jkHSCY+p1no8xErbV1Siz5QE8qKT1WIwybSWlqjw== + dependencies: + babel-helper-optimise-call-expression "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helpers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + integrity sha512-n7pFrqQm44TCYvrCDb0MqabAF+JUBq+ijBvNMUxpkLjJaAu32faIexewMumrH5KLLJ1HDyT0PTEqRyAe/GwwuQ== + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-messages@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + integrity sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w== + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-check-es2015-constants@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" + integrity sha512-B1M5KBP29248dViEo1owyY32lk1ZSH2DaNNrXLGt8lyjjHm7pBqAdQ7VKUPR6EEDO323+OvT3MQXbCin8ooWdA== + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-syntax-async-functions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" + integrity sha512-4Zp4unmHgw30A1eWI5EpACji2qMocisdXhAftfhXoSV9j0Tvj6nRFE3tOmRY912E0FMRm/L5xWE7MGVT2FoLnw== + +babel-plugin-syntax-exponentiation-operator@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" + integrity sha512-Z/flU+T9ta0aIEKl1tGEmN/pZiI1uXmCiGFRegKacQfEJzp7iNsKloZmyJlQr+75FCJtiFfGIK03SiCvCt9cPQ== + +babel-plugin-syntax-trailing-function-commas@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" + integrity sha512-Gx9CH3Q/3GKbhs07Bszw5fPTlU+ygrOGfAhEt7W2JICwufpC4SuO0mG0+4NykPBSYPMJhqvVlDBU17qB1D+hMQ== + +babel-plugin-transform-async-to-generator@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" + integrity sha512-7BgYJujNCg0Ti3x0c/DL3tStvnKS6ktIYOmo9wginv/dfZOrbSZ+qG4IRRHMBOzZ5Awb1skTiAsQXg/+IWkZYw== + dependencies: + babel-helper-remap-async-to-generator "^6.24.1" + babel-plugin-syntax-async-functions "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-arrow-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" + integrity sha512-PCqwwzODXW7JMrzu+yZIaYbPQSKjDTAsNNlK2l5Gg9g4rz2VzLnZsStvp/3c46GfXpwkyufb3NCyG9+50FF1Vg== + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" + integrity sha512-2+ujAT2UMBzYFm7tidUsYh+ZoIutxJ3pN9IYrF1/H6dCKtECfhmB8UkHVpyxDwkj0CYbQG35ykoz925TUnBc3A== + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoping@^6.23.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" + integrity sha512-YiN6sFAQ5lML8JjCmr7uerS5Yc/EMbgg9G8ZNmk2E3nYX4ckHR01wrkeeMijEf5WHNK5TW0Sl0Uu3pv3EdOJWw== + dependencies: + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-plugin-transform-es2015-classes@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" + integrity sha512-5Dy7ZbRinGrNtmWpquZKZ3EGY8sDgIVB4CU8Om8q8tnMLrD/m94cKglVcHps0BCTdZ0TJeeAWOq2TK9MIY6cag== + dependencies: + babel-helper-define-map "^6.24.1" + babel-helper-function-name "^6.24.1" + babel-helper-optimise-call-expression "^6.24.1" + babel-helper-replace-supers "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-computed-properties@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" + integrity sha512-C/uAv4ktFP/Hmh01gMTvYvICrKze0XVX9f2PdIXuriCSvUmV9j+u+BB9f5fJK3+878yMK6dkdcq+Ymr9mrcLzw== + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-destructuring@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" + integrity sha512-aNv/GDAW0j/f4Uy1OEPZn1mqD+Nfy9viFGBfQ5bZyT35YqOiqx7/tXdyfZkJ1sC21NyEsBdfDY6PYmLHF4r5iA== + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-duplicate-keys@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" + integrity sha512-ossocTuPOssfxO2h+Z3/Ea1Vo1wWx31Uqy9vIiJusOP4TbF7tPs9U0sJ9pX9OJPf4lXRGj5+6Gkl/HHKiAP5ug== + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-for-of@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" + integrity sha512-DLuRwoygCoXx+YfxHLkVx5/NpeSbVwfoTeBykpJK7JhYWlL/O8hgAK/reforUnZDlxasOrVPPJVI/guE3dCwkw== + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-function-name@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" + integrity sha512-iFp5KIcorf11iBqu/y/a7DK3MN5di3pNCzto61FqCNnUX4qeBwcV1SLqe10oXNnCaxBUImX3SckX2/o1nsrTcg== + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" + integrity sha512-tjFl0cwMPpDYyoqYA9li1/7mGFit39XiNX5DKC/uCNjBctMxyL1/PT/l4rSlbvBG1pOKI88STRdUsWXB3/Q9hQ== + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" + integrity sha512-LnIIdGWIKdw7zwckqx+eGjcS8/cl8D74A3BpJbGjKTFFNJSMrjN4bIh22HY1AlkUbeLG6X6OZj56BDvWD+OeFA== + dependencies: + babel-plugin-transform-es2015-modules-commonjs "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: + version "6.26.2" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" + integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q== + dependencies: + babel-plugin-transform-strict-mode "^6.24.1" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-types "^6.26.0" + +babel-plugin-transform-es2015-modules-systemjs@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" + integrity sha512-ONFIPsq8y4bls5PPsAWYXH/21Hqv64TBxdje0FvU3MhIV6QM2j5YS7KvAzg/nTIVLot2D2fmFQrFWCbgHlFEjg== + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-umd@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" + integrity sha512-LpVbiT9CLsuAIp3IG0tfbVo81QIhn6pE8xBJ7XSeCtFlMltuar5VuBV6y6Q45tpui9QWcy5i0vLQfCfrnF7Kiw== + dependencies: + babel-plugin-transform-es2015-modules-amd "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-object-super@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" + integrity sha512-8G5hpZMecb53vpD3mjs64NhI1au24TAmokQ4B+TBFBjN9cVoGoOvotdrMMRmHvVZUEvqGUPWL514woru1ChZMA== + dependencies: + babel-helper-replace-supers "^6.24.1" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-parameters@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" + integrity sha512-8HxlW+BB5HqniD+nLkQ4xSAVq3bR/pcYW9IigY+2y0dI+Y7INFeTbfAQr+63T3E4UDsZGjyb+l9txUnABWxlOQ== + dependencies: + babel-helper-call-delegate "^6.24.1" + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-shorthand-properties@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" + integrity sha512-mDdocSfUVm1/7Jw/FIRNw9vPrBQNePy6wZJlR8HAUBLybNp1w/6lr6zZ2pjMShee65t/ybR5pT8ulkLzD1xwiw== + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-spread@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" + integrity sha512-3Ghhi26r4l3d0Js933E5+IhHwk0A1yiutj9gwvzmFbVV0sPMYk2lekhOufHBswX7NCoSeF4Xrl3sCIuSIa+zOg== + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-sticky-regex@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" + integrity sha512-CYP359ADryTo3pCsH0oxRo/0yn6UsEZLqYohHmvLQdfS9xkf+MbCzE3/Kolw9OYIY4ZMilH25z/5CbQbwDD+lQ== + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-template-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" + integrity sha512-x8b9W0ngnKzDMHimVtTfn5ryimars1ByTqsfBDwAqLibmuuQY6pgBQi5z1ErIsUOWBdw1bW9FSz5RZUojM4apg== + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-typeof-symbol@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" + integrity sha512-fz6J2Sf4gYN6gWgRZaoFXmq93X+Li/8vf+fb0sGDVtdeWvxC9y5/bTD7bvfWMEq6zetGEHpWjtzRGSugt5kNqw== + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-unicode-regex@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" + integrity sha512-v61Dbbihf5XxnYjtBN04B/JBvsScY37R1cZT5r9permN1cp+b70DY3Ib3fIkgn1DI9U3tGgBJZVD8p/mE/4JbQ== + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + regexpu-core "^2.0.0" + +babel-plugin-transform-exponentiation-operator@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" + integrity sha512-LzXDmbMkklvNhprr20//RStKVcT8Cu+SQtX18eMHLhjHf2yFzwtQ0S2f0jQ+89rokoNdmwoSqYzAhq86FxlLSQ== + dependencies: + babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" + babel-plugin-syntax-exponentiation-operator "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-regenerator@^6.22.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" + integrity sha512-LS+dBkUGlNR15/5WHKe/8Neawx663qttS6AGqoOUhICc9d1KciBvtrQSuc0PI+CxQ2Q/S1aKuJ+u64GtLdcEZg== + dependencies: + regenerator-transform "^0.10.0" + +babel-plugin-transform-strict-mode@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" + integrity sha512-j3KtSpjyLSJxNoCDrhwiJad8kw0gJ9REGj8/CqL0HeRyLnvUNYV9zcqluL6QJSXh3nfsLEmSLvwRfGzrgR96Pw== + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-preset-env@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a" + integrity sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg== + dependencies: + babel-plugin-check-es2015-constants "^6.22.0" + babel-plugin-syntax-trailing-function-commas "^6.22.0" + babel-plugin-transform-async-to-generator "^6.22.0" + babel-plugin-transform-es2015-arrow-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoping "^6.23.0" + babel-plugin-transform-es2015-classes "^6.23.0" + babel-plugin-transform-es2015-computed-properties "^6.22.0" + babel-plugin-transform-es2015-destructuring "^6.23.0" + babel-plugin-transform-es2015-duplicate-keys "^6.22.0" + babel-plugin-transform-es2015-for-of "^6.23.0" + babel-plugin-transform-es2015-function-name "^6.22.0" + babel-plugin-transform-es2015-literals "^6.22.0" + babel-plugin-transform-es2015-modules-amd "^6.22.0" + babel-plugin-transform-es2015-modules-commonjs "^6.23.0" + babel-plugin-transform-es2015-modules-systemjs "^6.23.0" + babel-plugin-transform-es2015-modules-umd "^6.23.0" + babel-plugin-transform-es2015-object-super "^6.22.0" + babel-plugin-transform-es2015-parameters "^6.23.0" + babel-plugin-transform-es2015-shorthand-properties "^6.22.0" + babel-plugin-transform-es2015-spread "^6.22.0" + babel-plugin-transform-es2015-sticky-regex "^6.22.0" + babel-plugin-transform-es2015-template-literals "^6.22.0" + babel-plugin-transform-es2015-typeof-symbol "^6.23.0" + babel-plugin-transform-es2015-unicode-regex "^6.22.0" + babel-plugin-transform-exponentiation-operator "^6.22.0" + babel-plugin-transform-regenerator "^6.22.0" + browserslist "^3.2.6" + invariant "^2.2.2" + semver "^5.3.0" + +babel-register@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" + integrity sha512-veliHlHX06wjaeY8xNITbveXSiI+ASFnOqvne/LaIJIqOWi2Ogmj91KOugEz/hoh/fwMhXNBJPCv8Xaz5CyM4A== + dependencies: + babel-core "^6.26.0" + babel-runtime "^6.26.0" + core-js "^2.5.0" + home-or-tmp "^2.0.0" + lodash "^4.17.4" + mkdirp "^0.5.1" + source-map-support "^0.4.15" + +babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g== + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-template@^6.24.1, babel-template@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" + integrity sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg== + dependencies: + babel-runtime "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + lodash "^4.17.4" + +babel-traverse@^6.24.1, babel-traverse@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" + integrity sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA== + dependencies: + babel-code-frame "^6.26.0" + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + debug "^2.6.8" + globals "^9.18.0" + invariant "^2.2.2" + lodash "^4.17.4" + +babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + integrity sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g== + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babelify@^7.3.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/babelify/-/babelify-7.3.0.tgz#aa56aede7067fd7bd549666ee16dc285087e88e5" + integrity sha512-vID8Fz6pPN5pJMdlUnNFSfrlcx5MUule4k9aKs/zbZPyXxMTcRrB0M4Tarw22L8afr8eYSWxDPYCob3TdrqtlA== + dependencies: + babel-core "^6.0.14" + object-assign "^4.0.0" + +babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== + +backoff@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/backoff/-/backoff-2.5.0.tgz#f616eda9d3e4b66b8ca7fca79f695722c5f8e26f" + integrity sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA== + dependencies: + precond "0.2" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base-x@^3.0.2, base-x@^3.0.8: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + +bech32@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== + +bignumber.js@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.0.tgz#8d340146107fe3a6cb8d40699643c302e8773b62" + integrity sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bip39@2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/bip39/-/bip39-2.5.0.tgz#51cbd5179460504a63ea3c000db3f787ca051235" + integrity sha512-xwIx/8JKoT2+IPJpFEfXoWdYwP7UVAoUxxLNfGCfVowaJE7yg1Y5B1BVPqlUNsBq5/nGwmFkwRJ8xDW4sX8OdA== + dependencies: + create-hash "^1.1.0" + pbkdf2 "^3.0.9" + randombytes "^2.0.1" + safe-buffer "^5.0.1" + unorm "^1.3.3" + +blakejs@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" + integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== + +bluebird@^3.5.0, bluebird@^3.5.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +bn.js@4.11.6: + version "4.11.6" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" + integrity sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA== + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.8.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +body-parser@1.20.0, body-parser@^1.16.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" + integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== + dependencies: + bytes "3.1.2" + content-type "~1.0.4" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.10.3" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +brorand@^1.0.1, brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== + dependencies: + bn.js "^5.0.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" + integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== + dependencies: + bn.js "^5.1.1" + browserify-rsa "^4.0.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.3" + inherits "^2.0.4" + parse-asn1 "^5.1.5" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +browserslist@^3.2.6: + version "3.2.8" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6" + integrity sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ== + dependencies: + caniuse-lite "^1.0.30000844" + electron-to-chromium "^1.3.47" + +bs58@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== + dependencies: + base-x "^3.0.2" + +bs58check@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" + integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== + dependencies: + bs58 "^4.0.0" + create-hash "^1.1.0" + safe-buffer "^5.1.2" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer-to-arraybuffer@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz#6064a40fa76eb43c723aba9ef8f6e1216d10511a" + integrity sha512-3dthu5CYiVB1DEJp61FtApNnNndTckcqe4pFcLdvHtrpG+kcyekCJKg4MRiDcFW7A6AODnXB9U4dwQiCW5kzJQ== + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== + +buffer-xor@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-2.0.2.tgz#34f7c64f04c777a1f8aac5e661273bb9dd320289" + integrity sha512-eHslX0bin3GB+Lx2p7lEYRShRewuNZL3fUl4qlVJGGiwoPGftmt8JQgk2Y9Ji5/01TnVDo33E5b5O3vUB1HdqQ== + dependencies: + safe-buffer "^5.1.1" + +buffer@^5.0.5, buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +bufferutil@^4.0.1: + version "4.0.6" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.6.tgz#ebd6c67c7922a0e902f053e5d8be5ec850e48433" + integrity sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw== + dependencies: + node-gyp-build "^4.3.0" + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +bytewise-core@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/bytewise-core/-/bytewise-core-1.2.3.tgz#3fb410c7e91558eb1ab22a82834577aa6bd61d42" + integrity sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA== + dependencies: + typewise-core "^1.2" + +bytewise@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/bytewise/-/bytewise-1.1.0.tgz#1d13cbff717ae7158094aa881b35d081b387253e" + integrity sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ== + dependencies: + bytewise-core "^1.2.2" + typewise "^1.0.3" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + +cachedown@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cachedown/-/cachedown-1.0.0.tgz#d43f036e4510696b31246d7db31ebf0f7ac32d15" + integrity sha512-t+yVk82vQWCJF3PsWHMld+jhhjkkWjcAzz8NbFx1iULOXWl8Tm/FdM4smZNVw3MRr0X+lVTx9PKzvEn4Ng19RQ== + dependencies: + abstract-leveldown "^2.4.1" + lru-cache "^3.2.0" + +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ== + dependencies: + callsites "^2.0.0" + +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A== + dependencies: + caller-callsite "^2.0.0" + +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + integrity sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg== + +camelcase@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30000844: + version "1.0.30001378" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001378.tgz#3d2159bf5a8f9ca093275b0d3ecc717b00f27b67" + integrity sha512-JVQnfoO7FK7WvU4ZkBRbPjaot4+YqxogSDosHv0Hv5mWpUESmN+UubMU6L/hGz8QlQ2aY5U0vR6MOs6j/CXpNA== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== + +chai@^4.3.4: + version "4.3.6" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" + integrity sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.2" + deep-eql "^3.0.1" + get-func-name "^2.0.0" + loupe "^2.3.1" + pathval "^1.1.1" + type-detect "^4.0.5" + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0, chalk@^2.1.0, 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@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +check-error@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== + +checkpoint-store@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/checkpoint-store/-/checkpoint-store-1.1.0.tgz#04e4cb516b91433893581e6d4601a78e9552ea06" + integrity sha512-J/NdY2WvIx654cc6LWSq/IYFFCUf75fFTgwzFnmbqyORH4MwgiQCgswLLKBGzmsyTI5V7i5bp/So6sMbDWhedg== + dependencies: + functional-red-black-tree "^1.0.1" + +chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.2: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chownr@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +cids@^0.7.1: + version "0.7.5" + resolved "https://registry.yarnpkg.com/cids/-/cids-0.7.5.tgz#60a08138a99bfb69b6be4ceb63bfef7a396b28b2" + integrity sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA== + dependencies: + buffer "^5.5.0" + class-is "^1.1.0" + multibase "~0.6.0" + multicodec "^1.0.0" + multihashes "~0.4.15" + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +class-is@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/class-is/-/class-is-1.1.0.tgz#9d3c0fba0440d211d843cec3dedfa48055005825" + integrity sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw== + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +clean-stack@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-3.0.1.tgz#155bf0b2221bf5f4fba89528d24c5953f17fe3a8" + integrity sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg== + dependencies: + escape-string-regexp "4.0.0" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw== + dependencies: + restore-cursor "^2.0.0" + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cli-truncate@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389" + integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== + dependencies: + slice-ansi "^5.0.0" + string-width "^5.0.0" + +cli-width@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" + integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== + +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + integrity sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w== + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clone-response@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" + integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== + dependencies: + mimic-response "^1.0.0" + +clone@2.1.2, clone@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA== + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw== + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colorette@^2.0.16, colorette@^2.0.17: + version "2.0.19" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" + integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +command-exists@^1.2.8: + version "1.2.9" + resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" + integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== + +command-line-args@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-4.0.7.tgz#f8d1916ecb90e9e121eda6428e41300bfb64cc46" + integrity sha512-aUdPvQRAyBvQd2n7jXcsMDz68ckBJELXNzBybCHOibUWEg0mWTnaYCSRU8h9R+aNRSvDihJtssSRCiDRpLaezA== + dependencies: + array-back "^2.0.0" + find-replace "^1.0.3" + typical "^2.6.1" + +command-line-args@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e" + integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg== + dependencies: + array-back "^3.1.0" + find-replace "^3.0.0" + lodash.camelcase "^4.3.0" + typical "^4.0.0" + +command-line-usage@^6.1.0: + version "6.1.3" + resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.3.tgz#428fa5acde6a838779dfa30e44686f4b6761d957" + integrity sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw== + dependencies: + array-back "^4.0.2" + chalk "^2.4.2" + table-layout "^1.0.2" + typical "^5.2.0" + +commander@2.18.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" + integrity sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ== + +commander@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" + integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== + +commander@^8.1.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + +commander@^9.3.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.0.tgz#bc4a40918fefe52e22450c111ecd6b7acce6f11c" + integrity sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw== + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +concat-stream@^1.5.1: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-hash@^2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/content-hash/-/content-hash-2.5.2.tgz#bbc2655e7c21f14fd3bfc7b7d4bfe6e454c9e211" + integrity sha512-FvIQKy0S1JaWV10sMsA7TRx8bpU+pqPkhbsfvOJAdjRXvYxEckAwQWGwtRjiaJfh+E0DvcWUGqcdjwMGFjsSdw== + dependencies: + cids "^0.7.1" + multicodec "^0.5.5" + multihashes "^0.4.15" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +convert-source-map@^1.5.1: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +cookie@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + +cookiejar@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc" + integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ== + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== + +core-js-pure@^3.0.1: + version "3.24.1" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.24.1.tgz#8839dde5da545521bf282feb7dc6d0b425f39fd3" + integrity sha512-r1nJk41QLLPyozHUUPmILCEMtMw24NG4oWK6RbsDdjzQgg9ZvrUsPBj1MnG0wXXp1DCDU6j+wUvEmBSrtRbLXg== + +core-js@^2.4.0, core-js@^2.5.0: + version "2.6.12" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" + integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cors@^2.8.1: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +cosmiconfig@^5.0.7: + version "5.2.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.13.1" + parse-json "^4.0.0" + +crc-32@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + +create-ecdh@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== + dependencies: + bn.js "^4.1.0" + elliptic "^6.5.3" + +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-fetch@^2.1.0, cross-fetch@^2.1.1: + version "2.2.6" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.2.6.tgz#2ef0bb39a24ac034787965c457368a28730e220a" + integrity sha512-9JZz+vXCmfKUZ68zAptS7k4Nu8e2qcibe7WVZYps7sAgk5R8GYTc+T1WR0v1rlP9HxgARmOX1UTIJZFytajpNA== + dependencies: + node-fetch "^2.6.7" + whatwg-fetch "^2.0.4" + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-browserify@3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== + dependencies: + assert-plus "^1.0.0" + +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@4, debug@4.3.4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +debug@^3.1.0: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +decamelize@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== + +decompress-response@^3.2.0, decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== + dependencies: + mimic-response "^1.0.0" + +deep-eql@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== + dependencies: + type-detect "^4.0.0" + +deep-equal@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" + +deep-extend@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + +deferred-leveldown@~1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/deferred-leveldown/-/deferred-leveldown-1.2.2.tgz#3acd2e0b75d1669924bc0a4b642851131173e1eb" + integrity sha512-uukrWD2bguRtXilKt6cAWKyoXrTSMo5m7crUdLfWQmu8kIm88w3QZoUL+6nhpfKVmhHANER6Re3sKoNoZ3IKMA== + dependencies: + abstract-leveldown "~2.6.0" + +deferred-leveldown@~4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/deferred-leveldown/-/deferred-leveldown-4.0.2.tgz#0b0570087827bf480a23494b398f04c128c19a20" + integrity sha512-5fMC8ek8alH16QiV0lTCis610D1Zt1+LA4MS4d63JgS32lrCjTFDUFz2ao09/j2I4Bqb5jL4FZYwu7Jz0XO1ww== + dependencies: + abstract-leveldown "~5.0.0" + inherits "^2.0.3" + +deferred-leveldown@~5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz#27a997ad95408b61161aa69bd489b86c71b78058" + integrity sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw== + dependencies: + abstract-leveldown "~6.2.1" + inherits "^2.0.3" + +define-properties@^1.1.3, define-properties@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" + integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA== + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA== + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +defined@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +des.js@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + integrity sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A== + dependencies: + repeating "^2.0.0" + +diff@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-walk@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" + integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== + +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + +dotignore@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/dotignore/-/dotignore-0.1.2.tgz#f942f2200d28c3a76fbdd6f0ee9f3257c8a2e905" + integrity sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw== + dependencies: + minimatch "^3.0.4" + +duplexer3@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e" + integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.3.47: + version "1.4.224" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.224.tgz#ecf2eed395cfedcbbe634658ccc4b457f7b254c3" + integrity sha512-dOujC5Yzj0nOVE23iD5HKqrRSDj2SD7RazpZS/b/WX85MtO6/LzKDF4TlYZTBteB+7fvSg5JpWh0sN7fImNF8w== + +elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +emoji-regex@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.1.0.tgz#d50e383743c0f7a5945c47087295afc112e3cf66" + integrity sha512-xAEnNCT3w2Tg6MA7ly6QqYJvEoY1tm9iIjJ3yMKK9JPlWuRHAMoe5iETwQnx3M9TVbFMfsrBgWKR+IsmswwNjg== + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +encode-utf8@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" + integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +encoding-down@5.0.4, encoding-down@~5.0.0: + version "5.0.4" + resolved "https://registry.yarnpkg.com/encoding-down/-/encoding-down-5.0.4.tgz#1e477da8e9e9d0f7c8293d320044f8b2cd8e9614" + integrity sha512-8CIZLDcSKxgzT+zX8ZVfgNbu8Md2wq/iqa1Y7zyVR18QBEAc0Nmzuvj/N5ykSKpfGzjM8qxbaFntLPwnVoUhZw== + dependencies: + abstract-leveldown "^5.0.0" + inherits "^2.0.3" + level-codec "^9.0.0" + level-errors "^2.0.0" + xtend "^4.0.1" + +encoding-down@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/encoding-down/-/encoding-down-6.3.0.tgz#b1c4eb0e1728c146ecaef8e32963c549e76d082b" + integrity sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw== + dependencies: + abstract-leveldown "^6.2.1" + inherits "^2.0.3" + level-codec "^9.0.0" + level-errors "^2.0.0" + +encoding@^0.1.11: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enquirer@^2.3.0, enquirer@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +errno@~0.1.1: + version "0.1.8" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" + +error-ex@^1.2.0, error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.19.0, es-abstract@^1.19.2, es-abstract@^1.19.5, es-abstract@^1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814" + integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + function.prototype.name "^1.1.5" + get-intrinsic "^1.1.1" + get-symbol-description "^1.0.0" + has "^1.0.3" + has-property-descriptors "^1.0.0" + has-symbols "^1.0.3" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-weakref "^1.0.2" + object-inspect "^1.12.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + regexp.prototype.flags "^1.4.3" + string.prototype.trimend "^1.0.5" + string.prototype.trimstart "^1.0.5" + unbox-primitive "^1.0.2" + +es-array-method-boxes-properly@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" + integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es5-ext@^0.10.35, es5-ext@^0.10.50: + version "0.10.62" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" + integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-utils@^1.3.1: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint@^5.6.0: + version "5.16.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea" + integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.9.1" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^4.0.3" + eslint-utils "^1.3.1" + eslint-visitor-keys "^1.0.0" + espree "^5.0.1" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^11.7.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^6.2.2" + js-yaml "^3.13.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.11" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^5.5.1" + strip-ansi "^4.0.0" + strip-json-comments "^2.0.1" + table "^5.2.3" + text-table "^0.2.0" + +espree@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a" + integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A== + dependencies: + acorn "^6.0.7" + acorn-jsx "^5.0.0" + eslint-visitor-keys "^1.0.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +eth-block-tracker@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eth-block-tracker/-/eth-block-tracker-3.0.1.tgz#95cd5e763c7293e0b1b2790a2a39ac2ac188a5e1" + integrity sha512-WUVxWLuhMmsfenfZvFO5sbl1qFY2IqUlw/FPVmjjdElpqLsZtSG+wPe9Dz7W/sB6e80HgFKknOmKk2eNlznHug== + dependencies: + eth-query "^2.1.0" + ethereumjs-tx "^1.3.3" + ethereumjs-util "^5.1.3" + ethjs-util "^0.1.3" + json-rpc-engine "^3.6.0" + pify "^2.3.0" + tape "^4.6.3" + +eth-ens-namehash@2.0.8, eth-ens-namehash@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz#229ac46eca86d52e0c991e7cb2aef83ff0f68bcf" + integrity sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw== + dependencies: + idna-uts46-hx "^2.3.1" + js-sha3 "^0.5.7" + +eth-json-rpc-infura@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/eth-json-rpc-infura/-/eth-json-rpc-infura-3.2.1.tgz#26702a821067862b72d979c016fd611502c6057f" + integrity sha512-W7zR4DZvyTn23Bxc0EWsq4XGDdD63+XPUCEhV2zQvQGavDVC4ZpFDK4k99qN7bd7/fjj37+rxmuBOBeIqCA5Mw== + dependencies: + cross-fetch "^2.1.1" + eth-json-rpc-middleware "^1.5.0" + json-rpc-engine "^3.4.0" + json-rpc-error "^2.0.0" + +eth-json-rpc-middleware@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz#5c9d4c28f745ccb01630f0300ba945f4bef9593f" + integrity sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q== + dependencies: + async "^2.5.0" + eth-query "^2.1.2" + eth-tx-summary "^3.1.2" + ethereumjs-block "^1.6.0" + ethereumjs-tx "^1.3.3" + ethereumjs-util "^5.1.2" + ethereumjs-vm "^2.1.0" + fetch-ponyfill "^4.0.0" + json-rpc-engine "^3.6.0" + json-rpc-error "^2.0.0" + json-stable-stringify "^1.0.1" + promise-to-callback "^1.0.0" + tape "^4.6.3" + +eth-lib@0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.8.tgz#b194058bef4b220ad12ea497431d6cb6aa0623c8" + integrity sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw== + dependencies: + bn.js "^4.11.6" + elliptic "^6.4.0" + xhr-request-promise "^0.1.2" + +eth-lib@^0.1.26: + version "0.1.29" + resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.1.29.tgz#0c11f5060d42da9f931eab6199084734f4dbd1d9" + integrity sha512-bfttrr3/7gG4E02HoWTDUcDDslN003OlOoBxk9virpAZQ1ja/jDgwkWB8QfJF7ojuEowrqy+lzp9VcJG7/k5bQ== + dependencies: + bn.js "^4.11.6" + elliptic "^6.4.0" + nano-json-stream-parser "^0.1.2" + servify "^0.1.12" + ws "^3.0.0" + xhr-request-promise "^0.1.2" + +eth-query@^2.0.2, eth-query@^2.1.0, eth-query@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/eth-query/-/eth-query-2.1.2.tgz#d6741d9000106b51510c72db92d6365456a6da5e" + integrity sha512-srES0ZcvwkR/wd5OQBRA1bIJMww1skfGS0s8wlwK3/oNP4+wnds60krvu5R1QbpRQjMmpG5OMIWro5s7gvDPsA== + dependencies: + json-rpc-random-id "^1.0.0" + xtend "^4.0.1" + +eth-sig-util@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-3.0.0.tgz#75133b3d7c20a5731af0690c385e184ab942b97e" + integrity sha512-4eFkMOhpGbTxBQ3AMzVf0haUX2uTur7DpWiHzWyTURa28BVJJtOkcb9Ok5TV0YvEPG61DODPW7ZUATbJTslioQ== + dependencies: + buffer "^5.2.1" + elliptic "^6.4.0" + ethereumjs-abi "0.6.5" + ethereumjs-util "^5.1.1" + tweetnacl "^1.0.0" + tweetnacl-util "^0.15.0" + +eth-sig-util@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-1.4.2.tgz#8d958202c7edbaae839707fba6f09ff327606210" + integrity sha512-iNZ576iTOGcfllftB73cPB5AN+XUQAT/T8xzsILsghXC1o8gJUqe3RHlcDqagu+biFpYQ61KQrZZJza8eRSYqw== + dependencies: + ethereumjs-abi "git+https://github.com/ethereumjs/ethereumjs-abi.git" + ethereumjs-util "^5.1.1" + +eth-tx-summary@^3.1.2: + version "3.2.4" + resolved "https://registry.yarnpkg.com/eth-tx-summary/-/eth-tx-summary-3.2.4.tgz#e10eb95eb57cdfe549bf29f97f1e4f1db679035c" + integrity sha512-NtlDnaVZah146Rm8HMRUNMgIwG/ED4jiqk0TME9zFheMl1jOp6jL1m0NKGjJwehXQ6ZKCPr16MTr+qspKpEXNg== + dependencies: + async "^2.1.2" + clone "^2.0.0" + concat-stream "^1.5.1" + end-of-stream "^1.1.0" + eth-query "^2.0.2" + ethereumjs-block "^1.4.1" + ethereumjs-tx "^1.1.1" + ethereumjs-util "^5.0.1" + ethereumjs-vm "^2.6.0" + through2 "^2.0.3" + +ethashjs@~0.0.7: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ethashjs/-/ethashjs-0.0.8.tgz#227442f1bdee409a548fb04136e24c874f3aa6f9" + integrity sha512-/MSbf/r2/Ld8o0l15AymjOTlPqpN8Cr4ByUEA9GtR4x0yAh3TdtDzEg29zMjXCNPI7u6E5fOQdj/Cf9Tc7oVNw== + dependencies: + async "^2.1.2" + buffer-xor "^2.0.1" + ethereumjs-util "^7.0.2" + miller-rabin "^4.0.0" + +ethereum-bloom-filters@^1.0.6: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz#3ca07f4aed698e75bd134584850260246a5fed8a" + integrity sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA== + dependencies: + js-sha3 "^0.8.0" + +ethereum-common@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.2.0.tgz#13bf966131cce1eeade62a1b434249bb4cb120ca" + integrity sha512-XOnAR/3rntJgbCdGhqdaLIxDLWKLmsZOGhHdBKadEr6gEnJLH52k93Ou+TUdFaPN3hJc3isBZBal3U/XZ15abA== + +ethereum-common@^0.0.18: + version "0.0.18" + resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.0.18.tgz#2fdc3576f232903358976eb39da783213ff9523f" + integrity sha512-EoltVQTRNg2Uy4o84qpa2aXymXDJhxm7eos/ACOg0DG4baAbMjhbdAEsx9GeE8sC3XCxnYvrrzZDH8D8MtA2iQ== + +ethereum-cryptography@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz#8d6143cfc3d74bf79bbd8edecdf29e4ae20dd191" + integrity sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ== + dependencies: + "@types/pbkdf2" "^3.0.0" + "@types/secp256k1" "^4.0.1" + blakejs "^1.1.0" + browserify-aes "^1.2.0" + bs58check "^2.1.2" + create-hash "^1.2.0" + create-hmac "^1.1.7" + hash.js "^1.1.7" + keccak "^3.0.0" + pbkdf2 "^3.0.17" + randombytes "^2.1.0" + safe-buffer "^5.1.2" + scrypt-js "^3.0.0" + secp256k1 "^4.0.1" + setimmediate "^1.0.5" + +ethereum-cryptography@^1.0.3: + version "1.1.2" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-1.1.2.tgz#74f2ac0f0f5fe79f012c889b3b8446a9a6264e6d" + integrity sha512-XDSJlg4BD+hq9N2FjvotwUET9Tfxpxc3kWGE2AqUG5vcbeunnbImVk3cj6e/xT3phdW21mE8R5IugU4fspQDcQ== + dependencies: + "@noble/hashes" "1.1.2" + "@noble/secp256k1" "1.6.3" + "@scure/bip32" "1.1.0" + "@scure/bip39" "1.1.0" + +ethereum-waffle@^3.4.0: + version "3.4.4" + resolved "https://registry.yarnpkg.com/ethereum-waffle/-/ethereum-waffle-3.4.4.tgz#1378b72040697857b7f5e8f473ca8f97a37b5840" + integrity sha512-PA9+jCjw4WC3Oc5ocSMBj5sXvueWQeAbvCA+hUlb6oFgwwKyq5ka3bWQ7QZcjzIX+TdFkxP4IbFmoY2D8Dkj9Q== + dependencies: + "@ethereum-waffle/chai" "^3.4.4" + "@ethereum-waffle/compiler" "^3.4.4" + "@ethereum-waffle/mock-contract" "^3.4.4" + "@ethereum-waffle/provider" "^3.4.4" + ethers "^5.0.1" + +ethereumjs-abi@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz#5a637ef16ab43473fa72a29ad90871405b3f5241" + integrity sha512-rCjJZ/AE96c/AAZc6O3kaog4FhOsAViaysBxqJNy2+LHP0ttH0zkZ7nXdVHOAyt6lFwLO0nlCwWszysG/ao1+g== + dependencies: + bn.js "^4.10.0" + ethereumjs-util "^4.3.0" + +ethereumjs-abi@0.6.8, ethereumjs-abi@^0.6.8: + version "0.6.8" + resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz#71bc152db099f70e62f108b7cdfca1b362c6fcae" + integrity sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA== + dependencies: + bn.js "^4.11.8" + ethereumjs-util "^6.0.0" + +"ethereumjs-abi@git+https://github.com/ethereumjs/ethereumjs-abi.git": + version "0.6.8" + resolved "git+https://github.com/ethereumjs/ethereumjs-abi.git#ee3994657fa7a427238e6ba92a84d0b529bbcde0" + dependencies: + bn.js "^4.11.8" + ethereumjs-util "^6.0.0" + +ethereumjs-account@3.0.0, ethereumjs-account@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ethereumjs-account/-/ethereumjs-account-3.0.0.tgz#728f060c8e0c6e87f1e987f751d3da25422570a9" + integrity sha512-WP6BdscjiiPkQfF9PVfMcwx/rDvfZTjFKY0Uwc09zSQr9JfIVH87dYIJu0gNhBhpmovV4yq295fdllS925fnBA== + dependencies: + ethereumjs-util "^6.0.0" + rlp "^2.2.1" + safe-buffer "^5.1.1" + +ethereumjs-account@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/ethereumjs-account/-/ethereumjs-account-2.0.5.tgz#eeafc62de544cb07b0ee44b10f572c9c49e00a84" + integrity sha512-bgDojnXGjhMwo6eXQC0bY6UK2liSFUSMwwylOmQvZbSl/D7NXQ3+vrGO46ZeOgjGfxXmgIeVNDIiHw7fNZM4VA== + dependencies: + ethereumjs-util "^5.0.0" + rlp "^2.0.0" + safe-buffer "^5.1.1" + +ethereumjs-block@2.2.2, ethereumjs-block@^2.2.2, ethereumjs-block@~2.2.0, ethereumjs-block@~2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-2.2.2.tgz#c7654be7e22df489fda206139ecd63e2e9c04965" + integrity sha512-2p49ifhek3h2zeg/+da6XpdFR3GlqY3BIEiqxGF8j9aSRIgkb7M1Ky+yULBKJOu8PAZxfhsYA+HxUk2aCQp3vg== + dependencies: + async "^2.0.1" + ethereumjs-common "^1.5.0" + ethereumjs-tx "^2.1.1" + ethereumjs-util "^5.0.0" + merkle-patricia-tree "^2.1.2" + +ethereumjs-block@^1.2.2, ethereumjs-block@^1.4.1, ethereumjs-block@^1.6.0: + version "1.7.1" + resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-1.7.1.tgz#78b88e6cc56de29a6b4884ee75379b6860333c3f" + integrity sha512-B+sSdtqm78fmKkBq78/QLKJbu/4Ts4P2KFISdgcuZUPDm9x+N7qgBPIIFUGbaakQh8bzuquiRVbdmvPKqbILRg== + dependencies: + async "^2.0.1" + ethereum-common "0.2.0" + ethereumjs-tx "^1.2.2" + ethereumjs-util "^5.0.0" + merkle-patricia-tree "^2.1.2" + +ethereumjs-blockchain@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/ethereumjs-blockchain/-/ethereumjs-blockchain-4.0.4.tgz#30f2228dc35f6dcf94423692a6902604ae34960f" + integrity sha512-zCxaRMUOzzjvX78DTGiKjA+4h2/sF0OYL1QuPux0DHpyq8XiNoF5GYHtb++GUxVlMsMfZV7AVyzbtgcRdIcEPQ== + dependencies: + async "^2.6.1" + ethashjs "~0.0.7" + ethereumjs-block "~2.2.2" + ethereumjs-common "^1.5.0" + ethereumjs-util "^6.1.0" + flow-stoplight "^1.0.0" + level-mem "^3.0.1" + lru-cache "^5.1.1" + rlp "^2.2.2" + semaphore "^1.1.0" + +ethereumjs-common@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-1.5.0.tgz#d3e82fc7c47c0cef95047f431a99485abc9bb1cd" + integrity sha512-SZOjgK1356hIY7MRj3/ma5qtfr/4B5BL+G4rP/XSMYr2z1H5el4RX5GReYCKmQmYI/nSBmRnwrZ17IfHuG0viQ== + +ethereumjs-common@^1.1.0, ethereumjs-common@^1.3.2, ethereumjs-common@^1.5.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-1.5.2.tgz#2065dbe9214e850f2e955a80e650cb6999066979" + integrity sha512-hTfZjwGX52GS2jcVO6E2sx4YuFnf0Fhp5ylo4pEPhEffNln7vS59Hr5sLnp3/QCazFLluuBZ+FZ6J5HTp0EqCA== + +ethereumjs-tx@2.1.2, ethereumjs-tx@^2.1.1, ethereumjs-tx@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-2.1.2.tgz#5dfe7688bf177b45c9a23f86cf9104d47ea35fed" + integrity sha512-zZEK1onCeiORb0wyCXUvg94Ve5It/K6GD1K+26KfFKodiBiS6d9lfCXlUKGBBdQ+bv7Day+JK0tj1K+BeNFRAw== + dependencies: + ethereumjs-common "^1.5.0" + ethereumjs-util "^6.0.0" + +ethereumjs-tx@^1.1.1, ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.3: + version "1.3.7" + resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz#88323a2d875b10549b8347e09f4862b546f3d89a" + integrity sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA== + dependencies: + ethereum-common "^0.0.18" + ethereumjs-util "^5.0.0" + +ethereumjs-util@6.2.1, ethereumjs-util@^6.0.0, ethereumjs-util@^6.1.0, ethereumjs-util@^6.2.0, ethereumjs-util@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz#fcb4e4dd5ceacb9d2305426ab1a5cd93e3163b69" + integrity sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw== + dependencies: + "@types/bn.js" "^4.11.3" + bn.js "^4.11.0" + create-hash "^1.1.2" + elliptic "^6.5.2" + ethereum-cryptography "^0.1.3" + ethjs-util "0.1.6" + rlp "^2.2.3" + +ethereumjs-util@^4.3.0: + version "4.5.1" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-4.5.1.tgz#f4bf9b3b515a484e3cc8781d61d9d980f7c83bd0" + integrity sha512-WrckOZ7uBnei4+AKimpuF1B3Fv25OmoRgmYCpGsP7u8PFxXAmAgiJSYT2kRWnt6fVIlKaQlZvuwXp7PIrmn3/w== + dependencies: + bn.js "^4.8.0" + create-hash "^1.1.2" + elliptic "^6.5.2" + ethereum-cryptography "^0.1.3" + rlp "^2.0.0" + +ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereumjs-util@^5.1.2, ethereumjs-util@^5.1.3, ethereumjs-util@^5.1.5, ethereumjs-util@^5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz#a833f0e5fca7e5b361384dc76301a721f537bf65" + integrity sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ== + dependencies: + bn.js "^4.11.0" + create-hash "^1.1.2" + elliptic "^6.5.2" + ethereum-cryptography "^0.1.3" + ethjs-util "^0.1.3" + rlp "^2.0.0" + safe-buffer "^5.1.1" + +ethereumjs-util@^7.0.2, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.4, ethereumjs-util@^7.1.5: + version "7.1.5" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" + integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg== + dependencies: + "@types/bn.js" "^5.1.0" + bn.js "^5.1.2" + create-hash "^1.1.2" + ethereum-cryptography "^0.1.3" + rlp "^2.2.4" + +ethereumjs-vm@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-4.2.0.tgz#e885e861424e373dbc556278f7259ff3fca5edab" + integrity sha512-X6qqZbsY33p5FTuZqCnQ4+lo957iUJMM6Mpa6bL4UW0dxM6WmDSHuI4j/zOp1E2TDKImBGCJA9QPfc08PaNubA== + dependencies: + async "^2.1.2" + async-eventemitter "^0.2.2" + core-js-pure "^3.0.1" + ethereumjs-account "^3.0.0" + ethereumjs-block "^2.2.2" + ethereumjs-blockchain "^4.0.3" + ethereumjs-common "^1.5.0" + ethereumjs-tx "^2.1.2" + ethereumjs-util "^6.2.0" + fake-merkle-patricia-tree "^1.0.1" + functional-red-black-tree "^1.0.1" + merkle-patricia-tree "^2.3.2" + rustbn.js "~0.2.0" + safe-buffer "^5.1.1" + util.promisify "^1.0.0" + +ethereumjs-vm@^2.1.0, ethereumjs-vm@^2.3.4, ethereumjs-vm@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz#76243ed8de031b408793ac33907fb3407fe400c6" + integrity sha512-r/XIUik/ynGbxS3y+mvGnbOKnuLo40V5Mj1J25+HEO63aWYREIqvWeRO/hnROlMBE5WoniQmPmhiaN0ctiHaXw== + dependencies: + async "^2.1.2" + async-eventemitter "^0.2.2" + ethereumjs-account "^2.0.3" + ethereumjs-block "~2.2.0" + ethereumjs-common "^1.1.0" + ethereumjs-util "^6.0.0" + fake-merkle-patricia-tree "^1.0.1" + functional-red-black-tree "^1.0.1" + merkle-patricia-tree "^2.3.2" + rustbn.js "~0.2.0" + safe-buffer "^5.1.1" + +ethereumjs-wallet@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-0.6.5.tgz#685e9091645cee230ad125c007658833991ed474" + integrity sha512-MDwjwB9VQVnpp/Dc1XzA6J1a3wgHQ4hSvA1uWNatdpOrtCbPVuQSKSyRnjLvS0a+KKMw2pvQ9Ybqpb3+eW8oNA== + dependencies: + aes-js "^3.1.1" + bs58check "^2.1.2" + ethereum-cryptography "^0.1.3" + ethereumjs-util "^6.0.0" + randombytes "^2.0.6" + safe-buffer "^5.1.2" + scryptsy "^1.2.1" + utf8 "^3.0.0" + uuid "^3.3.2" + +ethers@^5.0.1, ethers@^5.0.2, ethers@^5.5.2: + version "5.6.9" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.6.9.tgz#4e12f8dfcb67b88ae7a78a9519b384c23c576a4d" + integrity sha512-lMGC2zv9HC5EC+8r429WaWu3uWJUCgUCt8xxKCFqkrFuBDZXDYIdzDUECxzjf2BMF8IVBByY1EBoGSL3RTm8RA== + dependencies: + "@ethersproject/abi" "5.6.4" + "@ethersproject/abstract-provider" "5.6.1" + "@ethersproject/abstract-signer" "5.6.2" + "@ethersproject/address" "5.6.1" + "@ethersproject/base64" "5.6.1" + "@ethersproject/basex" "5.6.1" + "@ethersproject/bignumber" "5.6.2" + "@ethersproject/bytes" "5.6.1" + "@ethersproject/constants" "5.6.1" + "@ethersproject/contracts" "5.6.2" + "@ethersproject/hash" "5.6.1" + "@ethersproject/hdnode" "5.6.2" + "@ethersproject/json-wallets" "5.6.1" + "@ethersproject/keccak256" "5.6.1" + "@ethersproject/logger" "5.6.0" + "@ethersproject/networks" "5.6.4" + "@ethersproject/pbkdf2" "5.6.1" + "@ethersproject/properties" "5.6.0" + "@ethersproject/providers" "5.6.8" + "@ethersproject/random" "5.6.1" + "@ethersproject/rlp" "5.6.1" + "@ethersproject/sha2" "5.6.1" + "@ethersproject/signing-key" "5.6.2" + "@ethersproject/solidity" "5.6.1" + "@ethersproject/strings" "5.6.1" + "@ethersproject/transactions" "5.6.2" + "@ethersproject/units" "5.6.1" + "@ethersproject/wallet" "5.6.2" + "@ethersproject/web" "5.6.1" + "@ethersproject/wordlists" "5.6.1" + +ethjs-unit@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" + integrity sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw== + dependencies: + bn.js "4.11.6" + number-to-bn "1.7.0" + +ethjs-util@0.1.6, ethjs-util@^0.1.3, ethjs-util@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" + integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w== + dependencies: + is-hex-prefixed "1.0.0" + strip-hex-prefix "1.0.0" + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +eventemitter3@4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" + integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== + +events@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +execa@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-6.1.0.tgz#cea16dee211ff011246556388effa0818394fb20" + integrity sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.1" + human-signals "^3.0.1" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^3.0.7" + strip-final-newline "^3.0.0" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA== + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +express@^4.14.0: + version "4.18.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" + integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.0" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.5.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.10.3" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +ext@^1.1.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.6.0.tgz#3871d50641e874cc172e2b53f919842d19db4c52" + integrity sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg== + dependencies: + type "^2.5.0" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + +fake-merkle-patricia-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fake-merkle-patricia-tree/-/fake-merkle-patricia-tree-1.0.1.tgz#4b8c3acfb520afadf9860b1f14cd8ce3402cddd3" + integrity sha512-Tgq37lkc9pUIgIKw5uitNUKcgcYL3R6JvXtKQbOf/ZSavXbidsksgp/pAY6p//uhw0I4yoMsvTSovvVIsk/qxA== + dependencies: + checkpoint-store "^1.1.0" + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.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-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +fetch-ponyfill@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/fetch-ponyfill/-/fetch-ponyfill-4.1.0.tgz#ae3ce5f732c645eab87e4ae8793414709b239893" + integrity sha512-knK9sGskIg2T7OnYLdZ2hZXn0CtDrAIBxYQLpmEf0BqfdWnwmM1weccUl5+4EdA44tzNSFAuxITPbXtPehUB3g== + dependencies: + node-fetch "~1.7.1" + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ== + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-replace@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-1.0.3.tgz#b88e7364d2d9c959559f388c66670d6130441fa0" + integrity sha512-KrUnjzDCD9426YnCP56zGYy/eieTnhtK6Vn++j+JJzmlsWWwEkDnsyVF575spT6HJ6Ow9tlbT3TQTDsa+O4UWA== + dependencies: + array-back "^1.0.4" + test-value "^2.1.0" + +find-replace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" + integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== + dependencies: + array-back "^3.0.1" + +find-up@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + integrity sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA== + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== + dependencies: + locate-path "^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" + integrity sha512-dVtfb0WuQG+8Ag2uWkbG79hOUzEsRrhBzgfn86g2sJPkzmcpGdghbNTfUKGTxymFrY/tLIodDzLoW9nOJ4FY8Q== + dependencies: + fs-extra "^4.0.3" + micromatch "^3.1.4" + +find-yarn-workspace-root@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== + dependencies: + micromatch "^4.0.2" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +flow-stoplight@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/flow-stoplight/-/flow-stoplight-1.0.0.tgz#4a292c5bcff8b39fa6cc0cb1a853d86f27eeff7b" + integrity sha512-rDjbZUKpN8OYhB0IE/vY/I8UWO/602IIJEU/76Tv4LvYnwHCk0BCsvz4eRr9n+FQcri7L5cyaXOo0+/Kh4HisA== + +fmix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fmix/-/fmix-0.1.0.tgz#c7bbf124dec42c9d191cfb947d0a9778dd986c0c" + integrity sha512-Y6hyofImk9JdzU8k5INtTXX1cu8LDlePWDFU5sftm9H+zKCr5SGrVjdhkvsim646cw5zD0nADj8oHyXMZmCZ9w== + dependencies: + imul "^1.0.0" + +follow-redirects@^1.12.1, follow-redirects@^1.14.0: + version "1.15.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" + integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== + +for-each@^0.3.3, for-each@~0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== + +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fp-ts@1.19.3: + version "1.19.3" + resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.19.3.tgz#261a60d1088fbff01f91256f91d21d0caaaaa96f" + integrity sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg== + +fp-ts@^1.0.0: + version "1.19.5" + resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.19.5.tgz#3da865e585dfa1fdfd51785417357ac50afc520a" + integrity sha512-wDNqTimnzs8QqpldiId9OavWK2NptormjXnRJTQecNjzwfyp6P/8s/zG8e4h3ja3oqkKaY72UlTjQYt/1yXf9A== + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA== + dependencies: + map-cache "^0.2.2" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-extra@^0.30.0: + version "0.30.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" + integrity sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + klaw "^1.0.0" + path-is-absolute "^1.0.0" + rimraf "^2.2.8" + +fs-extra@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^4.0.2, fs-extra@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" + integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^7.0.0, fs-extra@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^8.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-minipass@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== + dependencies: + minipass "^2.6.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +function.prototype.name@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" + integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + functions-have-names "^1.2.2" + +functional-red-black-tree@^1.0.1, functional-red-black-tree@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== + +functions-have-names@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +ganache-core@^2.13.2: + version "2.13.2" + resolved "https://registry.yarnpkg.com/ganache-core/-/ganache-core-2.13.2.tgz#27e6fc5417c10e6e76e2e646671869d7665814a3" + integrity sha512-tIF5cR+ANQz0+3pHWxHjIwHqFXcVo0Mb+kcsNhglNFALcYo49aQpnS9dqHartqPfMFjiHh/qFoD3mYK0d/qGgw== + dependencies: + abstract-leveldown "3.0.0" + async "2.6.2" + bip39 "2.5.0" + cachedown "1.0.0" + clone "2.1.2" + debug "3.2.6" + encoding-down "5.0.4" + eth-sig-util "3.0.0" + ethereumjs-abi "0.6.8" + ethereumjs-account "3.0.0" + ethereumjs-block "2.2.2" + ethereumjs-common "1.5.0" + ethereumjs-tx "2.1.2" + ethereumjs-util "6.2.1" + ethereumjs-vm "4.2.0" + heap "0.2.6" + keccak "3.0.1" + level-sublevel "6.6.4" + levelup "3.1.1" + lodash "4.17.20" + lru-cache "5.1.1" + merkle-patricia-tree "3.0.0" + patch-package "6.2.2" + seedrandom "3.0.1" + source-map-support "0.5.12" + tmp "0.1.0" + web3-provider-engine "14.2.1" + websocket "1.0.32" + optionalDependencies: + ethereumjs-wallet "0.6.5" + web3 "1.2.11" + +get-caller-file@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598" + integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ== + +get-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +get-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== + dependencies: + assert-plus "^1.0.0" + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.1.2, glob@^7.1.3, glob@^7.1.6, glob@~7.2.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global@~4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" + integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== + dependencies: + min-document "^2.19.0" + process "^0.11.10" + +globals@^11.7.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^9.18.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== + +globby@^11.0.0, globby@^11.0.1: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +got@9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + +got@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" + integrity sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw== + dependencies: + decompress-response "^3.2.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" + is-plain-obj "^1.1.0" + is-retry-allowed "^1.0.0" + is-stream "^1.0.0" + isurl "^1.0.0-alpha5" + lowercase-keys "^1.0.0" + p-cancelable "^0.3.0" + p-timeout "^1.1.1" + safe-buffer "^5.0.1" + timed-out "^4.0.0" + url-parse-lax "^1.0.0" + url-to-options "^1.0.1" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +handlebars@^4.7.6: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + +hardhat-deploy@^0.9.14: + version "0.9.29" + resolved "https://registry.yarnpkg.com/hardhat-deploy/-/hardhat-deploy-0.9.29.tgz#b1177d4f3077f335ad3f50c55825d9417ec75968" + integrity sha512-8tIGszPFmOaXtyloCbASiZPvoAgLNGGL/Ubys3YW/oj4dvoPa8G6YDyaOCdsAhsENZ+QgR280NFSG9JdN7SU9Q== + dependencies: + "@ethersproject/abi" "^5.4.0" + "@ethersproject/abstract-signer" "^5.4.1" + "@ethersproject/address" "^5.4.0" + "@ethersproject/bignumber" "^5.4.1" + "@ethersproject/bytes" "^5.4.0" + "@ethersproject/constants" "^5.4.0" + "@ethersproject/contracts" "^5.4.1" + "@ethersproject/providers" "^5.4.4" + "@ethersproject/solidity" "^5.4.0" + "@ethersproject/transactions" "^5.4.0" + "@ethersproject/wallet" "^5.4.0" + "@types/qs" "^6.9.7" + axios "^0.21.1" + chalk "^4.1.2" + chokidar "^3.5.2" + debug "^4.3.2" + enquirer "^2.3.6" + form-data "^4.0.0" + fs-extra "^10.0.0" + match-all "^1.2.6" + murmur-128 "^0.2.1" + qs "^6.9.4" + +hardhat@^2.7.1: + version "2.10.2" + resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.10.2.tgz#ff94ee4cb144a9c114641581ff5e4d7bea5f93a9" + integrity sha512-L/KvDDT/MA6332uAtYTqdcHoSABljw4pPjHQe5SHdIJ+xKfaSc6vDKw03CmrQ5Xup0gHs8XnVSBpZo1AbbIW7g== + dependencies: + "@ethereumjs/block" "^3.6.2" + "@ethereumjs/blockchain" "^5.5.2" + "@ethereumjs/common" "^2.6.4" + "@ethereumjs/tx" "^3.5.1" + "@ethereumjs/vm" "^5.9.0" + "@ethersproject/abi" "^5.1.2" + "@metamask/eth-sig-util" "^4.0.0" + "@sentry/node" "^5.18.1" + "@solidity-parser/parser" "^0.14.2" + "@types/bn.js" "^5.1.0" + "@types/lru-cache" "^5.1.0" + abort-controller "^3.0.0" + adm-zip "^0.4.16" + aggregate-error "^3.0.0" + ansi-escapes "^4.3.0" + chalk "^2.4.2" + chokidar "^3.4.0" + ci-info "^2.0.0" + debug "^4.1.1" + enquirer "^2.3.0" + env-paths "^2.2.0" + ethereum-cryptography "^1.0.3" + ethereumjs-abi "^0.6.8" + ethereumjs-util "^7.1.4" + find-up "^2.1.0" + fp-ts "1.19.3" + fs-extra "^7.0.1" + glob "7.2.0" + immutable "^4.0.0-rc.12" + io-ts "1.10.4" + lodash "^4.17.11" + merkle-patricia-tree "^4.2.4" + mnemonist "^0.38.0" + mocha "^10.0.0" + p-map "^4.0.0" + qs "^6.7.0" + raw-body "^2.4.1" + resolve "1.17.0" + semver "^6.3.0" + slash "^3.0.0" + solc "0.7.3" + source-map-support "^0.5.13" + stacktrace-parser "^0.1.10" + "true-case-path" "^2.2.1" + tsort "0.0.1" + undici "^5.4.0" + uuid "^8.3.2" + ws "^7.4.6" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== + dependencies: + ansi-regex "^2.0.0" + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-symbol-support-x@^1.4.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" + integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== + +has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-to-string-tag-x@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" + integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw== + dependencies: + has-symbol-support-x "^1.4.1" + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q== + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw== + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ== + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ== + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.3, has@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== + dependencies: + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +heap@0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.6.tgz#087e1f10b046932fc8594dd9e6d378afc9d1e5ac" + integrity sha512-MzzWcnfB1e4EG2vHi3dXHoBupmuXNZzx6pY6HldVS55JKKBoq3xOyzfSaZRkJp37HIhEYC78knabHff3zc4dQQ== + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +home-or-tmp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + integrity sha512-ycURW7oUxE2sNiPVw1HVEFsW+ecOpJ5zaj7eC0RlwhibhRBod20muUN8qu/gzx956YrLolVvs1MTXwKgC2rVEg== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.1" + +hosted-git-info@^2.1.4, hosted-git-info@^2.6.0: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-https@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/http-https/-/http-https-1.0.0.tgz#2f908dd5f1db4068c058cd6e6d4ce392c913389b" + integrity sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg== + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-3.0.1.tgz#c740920859dafa50e5a3222da9d3bf4bb0e5eef5" + integrity sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ== + +husky@^7.0.4: + version "7.0.4" + resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" + integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== + +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" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +idna-uts46-hx@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz#a1dc5c4df37eee522bf66d969cc980e00e8711f9" + integrity sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA== + dependencies: + punycode "2.1.0" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +immediate@^3.2.3: + version "3.3.0" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.3.0.tgz#1aef225517836bcdf7f2a2de2600c79ff0269266" + integrity sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q== + +immediate@~3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c" + integrity sha512-RrGCXRm/fRVqMIhqXrGEX9rRADavPiDFSoMb/k64i9XMk8uH4r/Omi5Ctierj6XzNecwDbO4WuFbDD1zmpl3Tg== + +immutable@^4.0.0-rc.12: + version "4.1.0" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef" + integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ== + +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg== + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + +import-fresh@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imul@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/imul/-/imul-1.0.1.tgz#9d5867161e8b3de96c2c38d5dc7cb102f35e2ac9" + integrity sha512-WFAgfwPLAjU66EKt6vRdTlKj4nAgIDQzh29JonLa4Bqtl6D8JrIMvWjCnx7xEjVNmP3U0fM5o8ZObk7d0f62bA== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inquirer@^6.2.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" + integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== + dependencies: + ansi-escapes "^3.2.0" + chalk "^2.4.2" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^2.0.0" + lodash "^4.17.12" + mute-stream "0.0.7" + run-async "^2.2.0" + rxjs "^6.4.0" + string-width "^2.1.0" + strip-ansi "^5.1.0" + through "^2.3.6" + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +invariant@^2.2.2: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + integrity sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ== + +io-ts@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-1.10.4.tgz#cd5401b138de88e4f920adbcb7026e2d1967e6e2" + integrity sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g== + dependencies: + fp-ts "^1.0.0" + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A== + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-arguments@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-core-module@^2.9.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" + integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== + dependencies: + has "^1.0.3" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg== + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + integrity sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw== + +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-finite@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" + integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== + +is-fn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fn/-/is-fn-1.0.0.tgz#9543d5de7bcf5b08a22ec8a20bae6e286d510d8c" + integrity sha512-XoFPJQmsAShb3jEQRfzf2rqXavq7fIqF/jOekp308JlThqrODnMpweVSGilKTCXELfLhltGP2AGgbQGVP8F1dg== + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw== + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-fullwidth-code-point@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" + integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== + +is-function@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" + integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-hex-prefixed@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" + integrity sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA== + +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg== + dependencies: + kind-of "^3.0.2" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" + integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA== + +is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== + +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-regex@^1.0.4, is-regex@^1.1.4, is-regex@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-retry-allowed@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" + integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== + +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + +is-stream@^1.0.0, is-stream@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== + +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typedarray@^1.0.0, is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-url@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" + integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q== + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== + +isarray@1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA== + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== + +isurl@^1.0.0-alpha5: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" + integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w== + dependencies: + has-to-string-tag-x "^1.2.0" + is-object "^1.0.1" + +js-sha3@0.8.0, js-sha3@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + +js-sha3@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" + integrity sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + integrity sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg== + +js-yaml@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +js-yaml@^3.12.0, js-yaml@^3.13.0, js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + integrity sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== + +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ== + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-rpc-engine@^3.4.0, json-rpc-engine@^3.6.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-3.8.0.tgz#9d4ff447241792e1d0a232f6ef927302bb0c62a9" + integrity sha512-6QNcvm2gFuuK4TKU1uwfH0Qd/cOSb9c1lls0gbnIhciktIUQJwz6NQNAW4B1KiGPenv7IKu97V222Yo1bNhGuA== + dependencies: + async "^2.0.1" + babel-preset-env "^1.7.0" + babelify "^7.3.0" + json-rpc-error "^2.0.0" + promise-to-callback "^1.0.0" + safe-event-emitter "^1.0.1" + +json-rpc-error@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/json-rpc-error/-/json-rpc-error-2.0.0.tgz#a7af9c202838b5e905c7250e547f1aff77258a02" + integrity sha512-EwUeWP+KgAZ/xqFpaP6YDAXMtCJi+o/QQpCQFIYyxr01AdADi2y413eM8hSqJcoQym9WMePAJWoaODEJufC4Ug== + dependencies: + inherits "^2.0.1" + +json-rpc-random-id@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-rpc-random-id/-/json-rpc-random-id-1.0.1.tgz#ba49d96aded1444dbb8da3d203748acbbcdec8c8" + integrity sha512-RJ9YYNCkhVDBuP4zN5BBtYAzEl03yq/jIIsyif0JY9qyJuQQZNeDK7anAPKKlyEtLSj2s8h6hNh2F8zO5q7ScA== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + integrity sha512-i/J297TW6xyj7sDFa7AmBPkQvLIxWr2kKPWI26tXydnZrzVAocNqn5DMNT1Mzk0vit1V5UkRM7C1KdVNp7Lmcg== + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + integrity sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw== + +json5@^2.1.3: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + +jsonfile@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" + integrity sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw== + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + integrity sha512-trvBk1ki43VZptdBI5rIlG4YOzyeH/WefQt5rj1grasPn4iiZWKet8nkgc4GlsAylaztn0qZfUYOiTsASJFdNA== + +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + +keccak@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.1.tgz#ae30a0e94dbe43414f741375cff6d64c8bea0bff" + integrity sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA== + dependencies: + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + +keccak@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.2.tgz#4c2c6e8c54e04f2670ee49fa734eb9da152206e0" + integrity sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ== + dependencies: + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + readable-stream "^3.6.0" + +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw== + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +klaw-sync@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== + dependencies: + graceful-fs "^4.1.11" + +klaw@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" + integrity sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw== + optionalDependencies: + graceful-fs "^4.1.9" + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + integrity sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw== + dependencies: + invert-kv "^1.0.0" + +level-codec@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-9.0.2.tgz#fd60df8c64786a80d44e63423096ffead63d8cbc" + integrity sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ== + dependencies: + buffer "^5.6.0" + +level-codec@~7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-7.0.1.tgz#341f22f907ce0f16763f24bddd681e395a0fb8a7" + integrity sha512-Ua/R9B9r3RasXdRmOtd+t9TCOEIIlts+TN/7XTT2unhDaL6sJn83S3rUyljbr6lVtw49N3/yA0HHjpV6Kzb2aQ== + +level-concat-iterator@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz#1d1009cf108340252cb38c51f9727311193e6263" + integrity sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw== + +level-errors@^1.0.3: + version "1.1.2" + resolved "https://registry.yarnpkg.com/level-errors/-/level-errors-1.1.2.tgz#4399c2f3d3ab87d0625f7e3676e2d807deff404d" + integrity sha512-Sw/IJwWbPKF5Ai4Wz60B52yj0zYeqzObLh8k1Tk88jVmD51cJSKWSYpRyhVIvFzZdvsPqlH5wfhp/yxdsaQH4w== + dependencies: + errno "~0.1.1" + +level-errors@^2.0.0, level-errors@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/level-errors/-/level-errors-2.0.1.tgz#2132a677bf4e679ce029f517c2f17432800c05c8" + integrity sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw== + dependencies: + errno "~0.1.1" + +level-errors@~1.0.3: + version "1.0.5" + resolved "https://registry.yarnpkg.com/level-errors/-/level-errors-1.0.5.tgz#83dbfb12f0b8a2516bdc9a31c4876038e227b859" + integrity sha512-/cLUpQduF6bNrWuAC4pwtUKA5t669pCsCi2XbmojG2tFeOr9j6ShtdDCtFFQO1DRt+EVZhx9gPzP9G2bUaG4ig== + dependencies: + errno "~0.1.1" + +level-iterator-stream@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/level-iterator-stream/-/level-iterator-stream-2.0.3.tgz#ccfff7c046dcf47955ae9a86f46dfa06a31688b4" + integrity sha512-I6Heg70nfF+e5Y3/qfthJFexhRw/Gi3bIymCoXAlijZdAcLaPuWSJs3KXyTYf23ID6g0o2QF62Yh+grOXY3Rig== + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.5" + xtend "^4.0.0" + +level-iterator-stream@~1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/level-iterator-stream/-/level-iterator-stream-1.3.1.tgz#e43b78b1a8143e6fa97a4f485eb8ea530352f2ed" + integrity sha512-1qua0RHNtr4nrZBgYlpV0qHHeHpcRRWTxEZJ8xsemoHAXNL5tbooh4tPEEqIqsbWCAJBmUmkwYK/sW5OrFjWWw== + dependencies: + inherits "^2.0.1" + level-errors "^1.0.3" + readable-stream "^1.0.33" + xtend "^4.0.0" + +level-iterator-stream@~3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/level-iterator-stream/-/level-iterator-stream-3.0.1.tgz#2c98a4f8820d87cdacab3132506815419077c730" + integrity sha512-nEIQvxEED9yRThxvOrq8Aqziy4EGzrxSZK+QzEFAVuJvQ8glfyZ96GB6BoI4sBbLfjMXm2w4vu3Tkcm9obcY0g== + dependencies: + inherits "^2.0.1" + readable-stream "^2.3.6" + xtend "^4.0.0" + +level-iterator-stream@~4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz#7ceba69b713b0d7e22fcc0d1f128ccdc8a24f79c" + integrity sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q== + dependencies: + inherits "^2.0.4" + readable-stream "^3.4.0" + xtend "^4.0.2" + +level-mem@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/level-mem/-/level-mem-3.0.1.tgz#7ce8cf256eac40f716eb6489654726247f5a89e5" + integrity sha512-LbtfK9+3Ug1UmvvhR2DqLqXiPW1OJ5jEh0a3m9ZgAipiwpSxGj/qaVVy54RG5vAQN1nCuXqjvprCuKSCxcJHBg== + dependencies: + level-packager "~4.0.0" + memdown "~3.0.0" + +level-mem@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/level-mem/-/level-mem-5.0.1.tgz#c345126b74f5b8aa376dc77d36813a177ef8251d" + integrity sha512-qd+qUJHXsGSFoHTziptAKXoLX87QjR7v2KMbqncDXPxQuCdsQlzmyX+gwrEHhlzn08vkf8TyipYyMmiC6Gobzg== + dependencies: + level-packager "^5.0.3" + memdown "^5.0.0" + +level-packager@^5.0.3: + version "5.1.1" + resolved "https://registry.yarnpkg.com/level-packager/-/level-packager-5.1.1.tgz#323ec842d6babe7336f70299c14df2e329c18939" + integrity sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ== + dependencies: + encoding-down "^6.3.0" + levelup "^4.3.2" + +level-packager@~4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/level-packager/-/level-packager-4.0.1.tgz#7e7d3016af005be0869bc5fa8de93d2a7f56ffe6" + integrity sha512-svCRKfYLn9/4CoFfi+d8krOtrp6RoX8+xm0Na5cgXMqSyRru0AnDYdLl+YI8u1FyS6gGZ94ILLZDE5dh2but3Q== + dependencies: + encoding-down "~5.0.0" + levelup "^3.0.0" + +level-post@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/level-post/-/level-post-1.0.7.tgz#19ccca9441a7cc527879a0635000f06d5e8f27d0" + integrity sha512-PWYqG4Q00asOrLhX7BejSajByB4EmG2GaKHfj3h5UmmZ2duciXLPGYWIjBzLECFWUGOZWlm5B20h/n3Gs3HKew== + dependencies: + ltgt "^2.1.2" + +level-sublevel@6.6.4: + version "6.6.4" + resolved "https://registry.yarnpkg.com/level-sublevel/-/level-sublevel-6.6.4.tgz#f7844ae893919cd9d69ae19d7159499afd5352ba" + integrity sha512-pcCrTUOiO48+Kp6F1+UAzF/OtWqLcQVTVF39HLdZ3RO8XBoXt+XVPKZO1vVr1aUoxHZA9OtD2e1v7G+3S5KFDA== + dependencies: + bytewise "~1.1.0" + level-codec "^9.0.0" + level-errors "^2.0.0" + level-iterator-stream "^2.0.3" + ltgt "~2.1.1" + pull-defer "^0.2.2" + pull-level "^2.0.3" + pull-stream "^3.6.8" + typewiselite "~1.0.0" + xtend "~4.0.0" + +level-supports@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-1.0.1.tgz#2f530a596834c7301622521988e2c36bb77d122d" + integrity sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg== + dependencies: + xtend "^4.0.2" + +level-ws@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/level-ws/-/level-ws-0.0.0.tgz#372e512177924a00424b0b43aef2bb42496d228b" + integrity sha512-XUTaO/+Db51Uiyp/t7fCMGVFOTdtLS/NIACxE/GHsij15mKzxksZifKVjlXDF41JMUP/oM1Oc4YNGdKnc3dVLw== + dependencies: + readable-stream "~1.0.15" + xtend "~2.1.1" + +level-ws@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/level-ws/-/level-ws-1.0.0.tgz#19a22d2d4ac57b18cc7c6ecc4bd23d899d8f603b" + integrity sha512-RXEfCmkd6WWFlArh3X8ONvQPm8jNpfA0s/36M4QzLqrLEIt1iJE9WBHLZ5vZJK6haMjJPJGJCQWfjMNnRcq/9Q== + dependencies: + inherits "^2.0.3" + readable-stream "^2.2.8" + xtend "^4.0.1" + +level-ws@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/level-ws/-/level-ws-2.0.0.tgz#207a07bcd0164a0ec5d62c304b4615c54436d339" + integrity sha512-1iv7VXx0G9ec1isqQZ7y5LmoZo/ewAsyDHNA8EFDW5hqH2Kqovm33nSFkSdnLLAK+I5FlT+lo5Cw9itGe+CpQA== + dependencies: + inherits "^2.0.3" + readable-stream "^3.1.0" + xtend "^4.0.1" + +levelup@3.1.1, levelup@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/levelup/-/levelup-3.1.1.tgz#c2c0b3be2b4dc316647c53b42e2f559e232d2189" + integrity sha512-9N10xRkUU4dShSRRFTBdNaBxofz+PGaIZO962ckboJZiNmLuhVT6FZ6ZKAsICKfUBO76ySaYU6fJWX/jnj3Lcg== + dependencies: + deferred-leveldown "~4.0.0" + level-errors "~2.0.0" + level-iterator-stream "~3.0.0" + xtend "~4.0.0" + +levelup@^1.2.1: + version "1.3.9" + resolved "https://registry.yarnpkg.com/levelup/-/levelup-1.3.9.tgz#2dbcae845b2bb2b6bea84df334c475533bbd82ab" + integrity sha512-VVGHfKIlmw8w1XqpGOAGwq6sZm2WwWLmlDcULkKWQXEA5EopA8OBNJ2Ck2v6bdk8HeEZSbCSEgzXadyQFm76sQ== + dependencies: + deferred-leveldown "~1.2.1" + level-codec "~7.0.0" + level-errors "~1.0.3" + level-iterator-stream "~1.3.0" + prr "~1.0.1" + semver "~5.4.1" + xtend "~4.0.0" + +levelup@^4.3.2: + version "4.4.0" + resolved "https://registry.yarnpkg.com/levelup/-/levelup-4.4.0.tgz#f89da3a228c38deb49c48f88a70fb71f01cafed6" + integrity sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ== + dependencies: + deferred-leveldown "~5.3.0" + level-errors "~2.0.0" + level-iterator-stream "~4.0.0" + level-supports "~1.0.0" + xtend "~4.0.0" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lilconfig@2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" + integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== + +lint-staged@>=10: + version "13.0.3" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.0.3.tgz#d7cdf03a3830b327a2b63c6aec953d71d9dc48c6" + integrity sha512-9hmrwSCFroTSYLjflGI8Uk+GWAwMB4OlpU4bMJEAT5d/llQwtYKoim4bLOyLCuWFAhWEupE0vkIFqtw/WIsPug== + dependencies: + cli-truncate "^3.1.0" + colorette "^2.0.17" + commander "^9.3.0" + debug "^4.3.4" + execa "^6.1.0" + lilconfig "2.0.5" + listr2 "^4.0.5" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-inspect "^1.12.2" + pidtree "^0.6.0" + string-argv "^0.3.1" + yaml "^2.1.1" + +listr2@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-4.0.5.tgz#9dcc50221583e8b4c71c43f9c7dfd0ef546b75d5" + integrity sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.16" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.5.5" + through "^2.3.8" + wrap-ansi "^7.0.0" + +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + integrity sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A== + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA== + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.assign@^4.0.3, lodash.assign@^4.0.6: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + integrity sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw== + +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + +lodash@4.17.20: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + +lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + +looper@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/looper/-/looper-2.0.0.tgz#66cd0c774af3d4fedac53794f742db56da8f09ec" + integrity sha512-6DzMHJcjbQX/UPHc1rRCBfKlLwDkvuGZ715cIR36wSdYqWXFT35uLXq5P/2orl3tz+t+VOVPxw4yPinQlUDGDQ== + +looper@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/looper/-/looper-3.0.0.tgz#2efa54c3b1cbaba9b94aee2e5914b0be57fbb749" + integrity sha512-LJ9wplN/uSn72oJRsXTx+snxPet5c8XiZmOKCm906NVYu+ag6SB6vUcnJcWxgnl2NfbIyeobAn7Bwv6xRj2XJg== + +loose-envify@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +loupe@^2.3.1: + version "2.3.4" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3" + integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ== + dependencies: + get-func-name "^2.0.0" + +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + +lru-cache@5.1.1, lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-3.2.0.tgz#71789b3b7f5399bec8565dda38aa30d2a097efee" + integrity sha512-91gyOKTc2k66UG6kHiH4h3S2eltcPwE1STVfMYC/NG+nZwf8IIuiamfmpGZjpbbxzSyEJaLC0tNSmhjlQUTJow== + dependencies: + pseudomap "^1.0.1" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +lru_map@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" + integrity sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ== + +ltgt@^2.1.2, ltgt@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5" + integrity sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA== + +ltgt@~2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.1.3.tgz#10851a06d9964b971178441c23c9e52698eece34" + integrity sha512-5VjHC5GsENtIi5rbJd+feEpDKhfr7j0odoUR2Uh978g+2p93nd5o34cTjQWohXsPsCZeqoDnIqEf88mPCe0Pfw== + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w== + dependencies: + object-visit "^1.0.0" + +match-all@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/match-all/-/match-all-1.2.6.tgz#66d276ad6b49655551e63d3a6ee53e8be0566f8d" + integrity sha512-0EESkXiTkWzrQQntBu2uzKvLu6vVkUGz40nGPbSZuegcfE5UuSzNjLaIu76zJWuaT/2I3Z/8M06OlUOZLGwLlQ== + +mcl-wasm@^0.7.1: + version "0.7.9" + resolved "https://registry.yarnpkg.com/mcl-wasm/-/mcl-wasm-0.7.9.tgz#c1588ce90042a8700c3b60e40efb339fc07ab87f" + integrity sha512-iJIUcQWA88IJB/5L15GnJVnSQJmf/YaxxV6zRavv83HILHaJQb6y0iFyDMdDO0gN8X37tdxmAOrH/P8B6RB8sQ== + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +memdown@^1.0.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/memdown/-/memdown-1.4.1.tgz#b4e4e192174664ffbae41361aa500f3119efe215" + integrity sha512-iVrGHZB8i4OQfM155xx8akvG9FIj+ht14DX5CQkCTG4EHzZ3d3sgckIf/Lm9ivZalEsFuEVnWv2B2WZvbrro2w== + dependencies: + abstract-leveldown "~2.7.1" + functional-red-black-tree "^1.0.1" + immediate "^3.2.3" + inherits "~2.0.1" + ltgt "~2.2.0" + safe-buffer "~5.1.1" + +memdown@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/memdown/-/memdown-5.1.0.tgz#608e91a9f10f37f5b5fe767667a8674129a833cb" + integrity sha512-B3J+UizMRAlEArDjWHTMmadet+UKwHd3UjMgGBkZcKAxAYVPS9o0Yeiha4qvz7iGiL2Sb3igUft6p7nbFWctpw== + dependencies: + abstract-leveldown "~6.2.1" + functional-red-black-tree "~1.0.1" + immediate "~3.2.3" + inherits "~2.0.1" + ltgt "~2.2.0" + safe-buffer "~5.2.0" + +memdown@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/memdown/-/memdown-3.0.0.tgz#93aca055d743b20efc37492e9e399784f2958309" + integrity sha512-tbV02LfZMWLcHcq4tw++NuqMO+FZX8tNJEiD2aNRm48ZZusVg5N8NART+dmBkepJVye986oixErf7jfXboMGMA== + dependencies: + abstract-leveldown "~5.0.0" + functional-red-black-tree "~1.0.1" + immediate "~3.2.3" + inherits "~2.0.1" + ltgt "~2.2.0" + safe-buffer "~5.1.1" + +memorystream@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" + integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw== + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +merkle-patricia-tree@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/merkle-patricia-tree/-/merkle-patricia-tree-3.0.0.tgz#448d85415565df72febc33ca362b8b614f5a58f8" + integrity sha512-soRaMuNf/ILmw3KWbybaCjhx86EYeBbD8ph0edQCTed0JN/rxDt1EBN52Ajre3VyGo+91f8+/rfPIRQnnGMqmQ== + dependencies: + async "^2.6.1" + ethereumjs-util "^5.2.0" + level-mem "^3.0.1" + level-ws "^1.0.0" + readable-stream "^3.0.6" + rlp "^2.0.0" + semaphore ">=1.0.1" + +merkle-patricia-tree@^2.1.2, merkle-patricia-tree@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/merkle-patricia-tree/-/merkle-patricia-tree-2.3.2.tgz#982ca1b5a0fde00eed2f6aeed1f9152860b8208a" + integrity sha512-81PW5m8oz/pz3GvsAwbauj7Y00rqm81Tzad77tHBwU7pIAtN+TJnMSOJhxBKflSVYhptMMb9RskhqHqrSm1V+g== + dependencies: + async "^1.4.2" + ethereumjs-util "^5.0.0" + level-ws "0.0.0" + levelup "^1.2.1" + memdown "^1.0.0" + readable-stream "^2.0.0" + rlp "^2.0.0" + semaphore ">=1.0.1" + +merkle-patricia-tree@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/merkle-patricia-tree/-/merkle-patricia-tree-4.2.4.tgz#ff988d045e2bf3dfa2239f7fabe2d59618d57413" + integrity sha512-eHbf/BG6eGNsqqfbLED9rIqbsF4+sykEaBn6OLNs71tjclbMcMOk1tEPmJKcNcNCLkvbpY/lwyOlizWsqPNo8w== + dependencies: + "@types/levelup" "^4.3.0" + ethereumjs-util "^7.1.4" + level-mem "^5.0.1" + level-ws "^2.0.0" + readable-stream "^3.6.0" + semaphore-async-await "^1.5.1" + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@^2.1.16, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +min-document@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" + integrity sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ== + dependencies: + dom-walk "^0.1.0" + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + +minimatch@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@~1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + +minipass@^2.6.0, minipass@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== + dependencies: + minipass "^2.9.0" + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp-promise@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1" + integrity sha512-Hepn5kb1lJPtVW84RFT40YG1OddBNTOVUZR2bzQUHc+Z03en8/3uX0+060JDhcEzyO08HmipsN9DcnFMxhIL9w== + dependencies: + mkdirp "*" + +mkdirp@*, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +mkdirp@^0.5.1, mkdirp@^0.5.5: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +mnemonist@^0.38.0: + version "0.38.5" + resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.38.5.tgz#4adc7f4200491237fe0fa689ac0b86539685cade" + integrity sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg== + dependencies: + obliterator "^2.0.0" + +mocha@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9" + integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA== + dependencies: + "@ungap/promise-all-settled" "1.1.2" + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.3" + debug "4.3.4" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.2.0" + he "1.2.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "5.0.1" + ms "2.1.3" + nanoid "3.3.3" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + workerpool "6.2.1" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + +mock-fs@^4.1.0: + version "4.14.0" + resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18" + integrity sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multibase@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/multibase/-/multibase-0.7.0.tgz#1adfc1c50abe05eefeb5091ac0c2728d6b84581b" + integrity sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg== + dependencies: + base-x "^3.0.8" + buffer "^5.5.0" + +multibase@~0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/multibase/-/multibase-0.6.1.tgz#b76df6298536cc17b9f6a6db53ec88f85f8cc12b" + integrity sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw== + dependencies: + base-x "^3.0.8" + buffer "^5.5.0" + +multicodec@^0.5.5: + version "0.5.7" + resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-0.5.7.tgz#1fb3f9dd866a10a55d226e194abba2dcc1ee9ffd" + integrity sha512-PscoRxm3f+88fAtELwUnZxGDkduE2HD9Q6GHUOywQLjOGT/HAdhjLDYNZ1e7VR0s0TP0EwZ16LNUTFpoBGivOA== + dependencies: + varint "^5.0.0" + +multicodec@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-1.0.4.tgz#46ac064657c40380c28367c90304d8ed175a714f" + integrity sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg== + dependencies: + buffer "^5.6.0" + varint "^5.0.0" + +multihashes@^0.4.15, multihashes@~0.4.15: + version "0.4.21" + resolved "https://registry.yarnpkg.com/multihashes/-/multihashes-0.4.21.tgz#dc02d525579f334a7909ade8a122dabb58ccfcb5" + integrity sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw== + dependencies: + buffer "^5.5.0" + multibase "^0.7.0" + varint "^5.0.0" + +murmur-128@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/murmur-128/-/murmur-128-0.2.1.tgz#a9f6568781d2350ecb1bf80c14968cadbeaa4b4d" + integrity sha512-WseEgiRkI6aMFBbj8Cg9yBj/y+OdipwVC7zUo3W2W1JAJITwouUOtpqsmGSg67EQmwwSyod7hsVsWY5LsrfQVg== + dependencies: + encode-utf8 "^1.0.2" + fmix "^0.1.0" + imul "^1.0.0" + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + integrity sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ== + +nano-json-stream-parser@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f" + integrity sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew== + +nanoid@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-addon-api@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" + integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== + +node-fetch@^2.6.1, node-fetch@^2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +node-fetch@~1.7.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + +node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40" + integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg== + +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-url@^4.1.0: + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== + +npm-run-path@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" + integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== + dependencies: + path-key "^4.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== + +number-to-bn@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" + integrity sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig== + dependencies: + bn.js "4.11.6" + strip-hex-prefix "1.0.0" + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^4, object-assign@^4.0.0, object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ== + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-inspect@^1.12.0, object-inspect@^1.12.2, object-inspect@^1.9.0, object-inspect@~1.12.2: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + +object-is@^1.0.1: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object-keys@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" + integrity sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw== + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA== + dependencies: + isobject "^3.0.0" + +object.assign@^4.1.2: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.getownpropertydescriptors@^2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz#7965e6437a57278b587383831a9b829455a4bc37" + integrity sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ== + dependencies: + array.prototype.reduce "^1.0.4" + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== + dependencies: + isobject "^3.0.1" + +obliterator@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-2.0.4.tgz#fa650e019b2d075d745e44f1effeb13a2adbe816" + integrity sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ== + +oboe@2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/oboe/-/oboe-2.1.4.tgz#20c88cdb0c15371bb04119257d4fdd34b0aa49f6" + integrity sha512-ymBJ4xSC6GBXLT9Y7lirj+xbqBLa+jADGJldGEYG7u8sZbS9GyG+u1Xk9c5cbriKwSpCg41qUhPjvU5xOpvIyQ== + dependencies: + http-https "^1.0.0" + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ== + dependencies: + mimic-fn "^1.0.0" + +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +open@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + +optionator@^0.8.2: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== + +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + integrity sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g== + dependencies: + lcid "^1.0.0" + +os-tmpdir@^1.0.1, os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + +p-cancelable@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" + integrity sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw== + +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg== + dependencies: + p-limit "^1.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-timeout@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" + integrity sha512-gb0ryzr+K2qFqFv6qi3khoeqMZF/+ajxQipEF6NteZVnvz9tzdsfAVj3lYtn1gAXvH5lfLwfxEII799gt/mRIA== + dependencies: + p-finally "^1.0.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-asn1@^5.0.0, parse-asn1@^5.1.5: + version "5.1.6" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" + integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== + dependencies: + asn1.js "^5.2.0" + browserify-aes "^1.0.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + +parse-headers@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9" + integrity sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA== + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ== + dependencies: + error-ex "^1.2.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== + +patch-package@6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.2.2.tgz#71d170d650c65c26556f0d0fbbb48d92b6cc5f39" + integrity sha512-YqScVYkVcClUY0v8fF0kWOjDYopzIM8e3bj/RU1DPeEF14+dCGm6UeOYm4jvCyxqIEQ5/eJzmbWfDWnUleFNMg== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + chalk "^2.4.2" + cross-spawn "^6.0.5" + find-yarn-workspace-root "^1.2.1" + fs-extra "^7.0.1" + is-ci "^2.0.0" + klaw-sync "^6.0.0" + minimist "^1.2.0" + rimraf "^2.6.3" + semver "^5.6.0" + slash "^2.0.0" + tmp "^0.0.33" + +patch-package@^6.2.2: + version "6.4.7" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" + integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + chalk "^2.4.2" + cross-spawn "^6.0.5" + find-yarn-workspace-root "^2.0.0" + fs-extra "^7.0.1" + is-ci "^2.0.0" + klaw-sync "^6.0.0" + minimist "^1.2.0" + open "^7.4.2" + rimraf "^2.6.3" + semver "^5.6.0" + slash "^2.0.0" + tmp "^0.0.33" + +path-browserify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + integrity sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ== + dependencies: + pinkie-promise "^2.0.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + +path-parse@^1.0.6, path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + integrity sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg== + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== + +pbkdf2@^3.0.17, pbkdf2@^3.0.3, pbkdf2@^3.0.9: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pidtree@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" + integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== + +pify@^2.0.0, pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== + +postinstall-postinstall@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" + integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== + +precond@0.2: + version "0.2.3" + resolved "https://registry.yarnpkg.com/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac" + integrity sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== + +prepend-http@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + integrity sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg== + +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== + +prettier-plugin-solidity@^1.0.0-beta.19: + version "1.0.0-beta.24" + resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.0.0-beta.24.tgz#67573ca87098c14f7ccff3639ddd8a4cab2a87eb" + integrity sha512-6JlV5BBTWzmDSq4kZ9PTXc3eLOX7DF5HpbqmmaF+kloyUwOZbJ12hIYsUaZh2fVgZdV2t0vWcvY6qhILhlzgqg== + dependencies: + "@solidity-parser/parser" "^0.14.3" + emoji-regex "^10.1.0" + escape-string-regexp "^4.0.0" + semver "^7.3.7" + solidity-comments-extractor "^0.0.7" + string-width "^4.2.3" + +prettier@^1.14.3: + version "1.19.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" + integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== + +prettier@^2.1.2, prettier@^2.5.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" + integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== + +private@^0.1.6, private@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +promise-to-callback@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/promise-to-callback/-/promise-to-callback-1.0.0.tgz#5d2a749010bfb67d963598fcd3960746a68feef7" + integrity sha512-uhMIZmKM5ZteDMfLgJnoSq9GCwsNKrYau73Awf1jIy6/eUcuuZ3P+CD9zUv0kJsIUbU+x6uLNIhXhLHDs1pNPA== + dependencies: + is-fn "^1.0.0" + set-immediate-shim "^1.0.1" + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== + +pseudomap@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== + +psl@^1.1.28: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + +pull-cat@^1.1.9: + version "1.1.11" + resolved "https://registry.yarnpkg.com/pull-cat/-/pull-cat-1.1.11.tgz#b642dd1255da376a706b6db4fa962f5fdb74c31b" + integrity sha512-i3w+xZ3DCtTVz8S62hBOuNLRHqVDsHMNZmgrZsjPnsxXUgbWtXEee84lo1XswE7W2a3WHyqsNuDJTjVLAQR8xg== + +pull-defer@^0.2.2: + version "0.2.3" + resolved "https://registry.yarnpkg.com/pull-defer/-/pull-defer-0.2.3.tgz#4ee09c6d9e227bede9938db80391c3dac489d113" + integrity sha512-/An3KE7mVjZCqNhZsr22k1Tx8MACnUnHZZNPSJ0S62td8JtYr/AiRG42Vz7Syu31SoTLUzVIe61jtT/pNdjVYA== + +pull-level@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pull-level/-/pull-level-2.0.4.tgz#4822e61757c10bdcc7cf4a03af04c92734c9afac" + integrity sha512-fW6pljDeUThpq5KXwKbRG3X7Ogk3vc75d5OQU/TvXXui65ykm+Bn+fiktg+MOx2jJ85cd+sheufPL+rw9QSVZg== + dependencies: + level-post "^1.0.7" + pull-cat "^1.1.9" + pull-live "^1.0.1" + pull-pushable "^2.0.0" + pull-stream "^3.4.0" + pull-window "^2.1.4" + stream-to-pull-stream "^1.7.1" + +pull-live@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pull-live/-/pull-live-1.0.1.tgz#a4ecee01e330155e9124bbbcf4761f21b38f51f5" + integrity sha512-tkNz1QT5gId8aPhV5+dmwoIiA1nmfDOzJDlOOUpU5DNusj6neNd3EePybJ5+sITr2FwyCs/FVpx74YMCfc8YeA== + dependencies: + pull-cat "^1.1.9" + pull-stream "^3.4.0" + +pull-pushable@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pull-pushable/-/pull-pushable-2.2.0.tgz#5f2f3aed47ad86919f01b12a2e99d6f1bd776581" + integrity sha512-M7dp95enQ2kaHvfCt2+DJfyzgCSpWVR2h2kWYnVsW6ZpxQBx5wOu0QWOvQPVoPnBLUZYitYP2y7HyHkLQNeGXg== + +pull-stream@^3.2.3, pull-stream@^3.4.0, pull-stream@^3.6.8: + version "3.6.14" + resolved "https://registry.yarnpkg.com/pull-stream/-/pull-stream-3.6.14.tgz#529dbd5b86131f4a5ed636fdf7f6af00781357ee" + integrity sha512-KIqdvpqHHaTUA2mCYcLG1ibEbu/LCKoJZsBWyv9lSYtPkJPBq8m3Hxa103xHi6D2thj5YXa0TqK3L3GUkwgnew== + +pull-window@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/pull-window/-/pull-window-2.1.4.tgz#fc3b86feebd1920c7ae297691e23f705f88552f0" + integrity sha512-cbDzN76BMlcGG46OImrgpkMf/VkCnupj8JhsrpBw3aWBM9ye345aYnqitmZCgauBkc0HbbRRn9hCnsa3k2FNUg== + dependencies: + looper "^2.0.0" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== + +punycode@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" + integrity sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA== + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@6.10.3: + version "6.10.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" + integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== + dependencies: + side-channel "^1.0.4" + +qs@^6.7.0, qs@^6.9.4: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +qs@~6.5.2: + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== + +query-string@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" + integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== + dependencies: + decode-uri-component "^0.2.0" + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.0.6, randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.1, raw-body@^2.4.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + integrity sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A== + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + integrity sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ== + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + +readable-stream@^1.0.33: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + integrity sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.2.2, readable-stream@^2.2.8, readable-stream@^2.2.9, readable-stream@^2.3.6, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6, readable-stream@^3.1.0, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@~1.0.15: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + integrity sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +reduce-flatten@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" + integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== + +regenerate@^1.2.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +regenerator-transform@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" + integrity sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q== + dependencies: + babel-runtime "^6.18.0" + babel-types "^6.19.0" + private "^0.1.6" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" + integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + functions-have-names "^1.2.2" + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +regexpu-core@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" + integrity sha512-tJ9+S4oKjxY8IZ9jmjnp/mtytu1u3iyIQAfmI51IKWH6bFf7XR1ybtaO6j7INhZKXOTYADk7V5qxaqLkmNxiZQ== + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + +regjsgen@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" + integrity sha512-x+Y3yA24uF68m5GA+tBjbGYo64xXVJpbToBaWCoSNSc1hdk6dfctaRWrNFTVJZIIhL5GxW8zwjoixbnifnK59g== + +regjsparser@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + integrity sha512-jlQ9gYLfk2p3V5Ag5fYhA7fv7OHzd1KUH0PRP46xc3TgwjwgROIW572AfYg/X9kaNq/LJnu6oJcFRXlIrGoTRw== + dependencies: + jsesc "~0.5.0" + +repeat-element@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" + integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A== + dependencies: + is-finite "^1.0.0" + +request@^2.79.0, request@^2.85.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-from-string@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418" + integrity sha512-H7AkJWMobeskkttHyhTVtS0fxpFLjxhbfMa6Bk3wimP7sdPRGL3EyCg3sAQenFfAe+xQ+oAc85Nmtvq0ROM83Q== + +require-from-string@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + integrity sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug== + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== + +resolve@1.17.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + dependencies: + path-parse "^1.0.6" + +resolve@^1.10.0, resolve@^1.8.1, resolve@~1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ== + dependencies: + lowercase-keys "^1.0.0" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q== + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +resumer@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759" + integrity sha512-Fn9X8rX8yYF4m81rZCK/5VmrmsSbqS/i3rDLl6ZZHAXgC2nTAx3dhwG8q8odP/RmdLa2YrybDJaAMg+X1ajY3w== + dependencies: + through "~2.3.4" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +rimraf@^2.2.8, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +rlp@^2.0.0, rlp@^2.2.1, rlp@^2.2.2, rlp@^2.2.3, rlp@^2.2.4: + version "2.2.7" + resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf" + integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ== + dependencies: + bn.js "^5.2.0" + +run-async@^2.2.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rustbn.js@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/rustbn.js/-/rustbn.js-0.2.0.tgz#8082cb886e707155fd1cb6f23bd591ab8d55d0ca" + integrity sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA== + +rxjs@^6.4.0: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + +rxjs@^7.5.5: + version "7.5.6" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.6.tgz#0446577557862afd6903517ce7cae79ecb9662bc" + integrity sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw== + dependencies: + tslib "^2.1.0" + +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-event-emitter@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-event-emitter/-/safe-event-emitter-1.0.1.tgz#5b692ef22329ed8f69fdce607e50ca734f6f20af" + integrity sha512-e1wFe99A91XYYxoQbcq2ZJUWurxEyP8vfz7A7vuUe1s95q8r5ebraVaA1BukYJcpM6V16ugWoD9vngi8Ccu5fg== + dependencies: + events "^3.0.0" + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +scrypt-js@3.0.1, scrypt-js@^3.0.0, scrypt-js@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" + integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== + +scryptsy@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/scryptsy/-/scryptsy-1.2.1.tgz#a3225fa4b2524f802700761e2855bdf3b2d92163" + integrity sha512-aldIRgMozSJ/Gl6K6qmJZysRP82lz83Wb42vl4PWN8SaLFHIaOzLPc9nUUW2jQN88CuGm5q5HefJ9jZ3nWSmTw== + dependencies: + pbkdf2 "^3.0.3" + +secp256k1@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" + integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA== + dependencies: + elliptic "^6.5.4" + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + +seedrandom@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.1.tgz#eb3dde015bcf55df05a233514e5df44ef9dce083" + integrity sha512-1/02Y/rUeU1CJBAGLebiC5Lbo5FnB22gQbIFFYTLkwvp1xdABZJH1sn4ZT1MzXmPpzv+Rf/Lu2NcsLJiK4rcDg== + +semaphore-async-await@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/semaphore-async-await/-/semaphore-async-await-1.5.1.tgz#857bef5e3644601ca4b9570b87e9df5ca12974fa" + integrity sha512-b/ptP11hETwYWpeilHXXQiV5UJNJl7ZWWooKRE5eBIYWoom6dZ0SluCIdCtKycsMtZgKWE01/qAw6jblw1YVhg== + +semaphore@>=1.0.1, semaphore@^1.0.3, semaphore@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.1.0.tgz#aaad8b86b20fe8e9b32b16dc2ee682a8cd26a8aa" + integrity sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA== + +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.3.2, semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + +semver@~5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" + integrity sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg== + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serialize-javascript@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +servify@^0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/servify/-/servify-0.1.12.tgz#142ab7bee1f1d033b66d0707086085b17c06db95" + integrity sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw== + dependencies: + body-parser "^1.16.0" + cors "^2.8.1" + express "^4.14.0" + request "^2.79.0" + xhr "^2.3.3" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +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" + integrity sha512-Li5AOqrZWCVA2n5kryzEmqai6bKSIvpz5oUJHPVj6+dsbD3X1ixtsY5tEnsaNpH3pFAHmG8eIHUrtEtohrg+UQ== + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.2, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^2.7.0: + version "2.8.2" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.2.tgz#5708fb0919d440657326cd5fe7d2599d07705019" + integrity sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw== + dependencies: + decompress-response "^3.3.0" + once "^1.3.1" + simple-concat "^1.0.0" + +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + integrity sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg== + +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" + integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== + dependencies: + ansi-styles "^6.0.0" + is-fullwidth-code-point "^4.0.0" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +"solc-0.8@npm:solc@^0.8.0": + version "0.8.16" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.16.tgz#120f992357e236d99e6cf3445bf2c2dca3384f96" + integrity sha512-6oZg7FAhIouj2zYLvoR3Q4fMP/+BGPR7sY7GcrEXKIp+DRd8RmpDEFO1LUBKpClUiaYguNgmthTFmnPl4MeiMQ== + dependencies: + command-exists "^1.2.8" + commander "^8.1.0" + follow-redirects "^1.12.1" + js-sha3 "0.8.0" + memorystream "^0.3.1" + semver "^5.5.0" + tmp "0.0.33" + +solc@0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.7.3.tgz#04646961bd867a744f63d2b4e3c0701ffdc7d78a" + integrity sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA== + dependencies: + command-exists "^1.2.8" + commander "3.0.2" + follow-redirects "^1.12.1" + fs-extra "^0.30.0" + js-sha3 "0.8.0" + memorystream "^0.3.1" + require-from-string "^2.0.0" + semver "^5.5.0" + tmp "0.0.33" + +solc@^0.4.20: + version "0.4.26" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.4.26.tgz#5390a62a99f40806b86258c737c1cf653cc35cb5" + integrity sha512-o+c6FpkiHd+HPjmjEVpQgH7fqZ14tJpXhho+/bQXlXbliLIS/xjXb42Vxh+qQY1WCSTMQ0+a5vR9vi0MfhU6mA== + dependencies: + fs-extra "^0.30.0" + memorystream "^0.3.1" + require-from-string "^1.1.0" + semver "^5.3.0" + yargs "^4.7.1" + +solc@^0.6.3, solc@^0.6.7: + version "0.6.12" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.6.12.tgz#48ac854e0c729361b22a7483645077f58cba080e" + integrity sha512-Lm0Ql2G9Qc7yPP2Ba+WNmzw2jwsrd3u4PobHYlSOxaut3TtUbj9+5ZrT6f4DUpNPEoBaFUOEg9Op9C0mk7ge9g== + dependencies: + command-exists "^1.2.8" + commander "3.0.2" + fs-extra "^0.30.0" + js-sha3 "0.8.0" + memorystream "^0.3.1" + require-from-string "^2.0.0" + semver "^5.5.0" + tmp "0.0.33" + +solhint@^3.3.6: + version "3.3.7" + resolved "https://registry.yarnpkg.com/solhint/-/solhint-3.3.7.tgz#b5da4fedf7a0fee954cb613b6c55a5a2b0063aa7" + integrity sha512-NjjjVmXI3ehKkb3aNtRJWw55SUVJ8HMKKodwe0HnejA+k0d2kmhw7jvpa+MCTbcEgt8IWSwx0Hu6aCo/iYOZzQ== + dependencies: + "@solidity-parser/parser" "^0.14.1" + ajv "^6.6.1" + antlr4 "4.7.1" + ast-parents "0.0.1" + chalk "^2.4.2" + commander "2.18.0" + cosmiconfig "^5.0.7" + eslint "^5.6.0" + fast-diff "^1.1.2" + glob "^7.1.3" + ignore "^4.0.6" + js-yaml "^3.12.0" + lodash "^4.17.11" + semver "^6.3.0" + optionalDependencies: + prettier "^1.14.3" + +solidity-comments-extractor@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" + integrity sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw== + +solidity-docgen@0.5.16: + version "0.5.16" + resolved "https://registry.yarnpkg.com/solidity-docgen/-/solidity-docgen-0.5.16.tgz#c12a5cac7ca656ff01b10dd832891983bb071f50" + integrity sha512-rFVpqSNnDGKvL68mPf4J9mEQIl+Ixy6bIz/YE6AgjBCPtrlm4KjWQhcBMQWc/LarSCenOpzhbG1tHqP9gf9kcg== + dependencies: + "@oclif/command" "^1.8.0" + "@oclif/config" "^1.17.0" + "@oclif/errors" "^1.3.3" + "@oclif/plugin-help" "^3.2.0" + globby "^11.0.0" + handlebars "^4.7.6" + json5 "^2.1.3" + lodash "^4.17.15" + micromatch "^4.0.2" + minimatch "^3.0.4" + semver "^7.3.2" + solc "^0.6.7" + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@0.5.12: + version "0.5.12" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" + integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-support@^0.4.15: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== + dependencies: + source-map "^0.5.6" + +source-map-support@^0.5.13: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== + +source-map@^0.5.6, source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.11" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" + integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +sshpk@^1.7.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" + integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stacktrace-parser@^0.1.10: + version "0.1.10" + resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a" + integrity sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg== + dependencies: + type-fest "^0.7.1" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g== + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +stream-to-pull-stream@^1.7.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/stream-to-pull-stream/-/stream-to-pull-stream-1.7.3.tgz#4161aa2d2eb9964de60bfa1af7feaf917e874ece" + integrity sha512-6sNyqJpr5dIOQdgNy/xcDWwDuzAsAwVzhzrWlAPAQ7Lkjx/rv0wgvxEyKwTq6FmNd5rjTrELt/CLmaSw7crMGg== + dependencies: + looper "^3.0.0" + pull-stream "^3.2.3" + +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ== + +string-argv@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" + integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== + +string-format@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" + integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw== + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string-width@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string.prototype.trim@~1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.6.tgz#824960787db37a9e24711802ed0c1d1c0254f83e" + integrity sha512-8lMR2m+U0VJTPp6JjvJTtGyc4FIGq9CdRt7O9p6T0e6K4vjU+OP+SQJpbe/SBmRcCUIvNUnjsbmY6lnMp8MhsQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + +string.prototype.trimend@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" + integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + +string.prototype.trimstart@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" + integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow== + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" + integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + integrity sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g== + dependencies: + is-utf8 "^0.2.0" + +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + +strip-hex-prefix@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" + integrity sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A== + dependencies: + is-hex-prefixed "1.0.0" + +strip-json-comments@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strip-json-comments@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +supports-color@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +swarm-js@^0.1.40: + version "0.1.40" + resolved "https://registry.yarnpkg.com/swarm-js/-/swarm-js-0.1.40.tgz#b1bc7b6dcc76061f6c772203e004c11997e06b99" + integrity sha512-yqiOCEoA4/IShXkY3WKwP5PvZhmoOOD8clsKA7EEcRILMkTEYHCQ21HDCAcVpmIxZq4LyZvWeRJ6quIyHk1caA== + dependencies: + bluebird "^3.5.0" + buffer "^5.0.5" + eth-lib "^0.1.26" + fs-extra "^4.0.2" + got "^7.1.0" + mime-types "^2.1.16" + mkdirp-promise "^5.0.1" + mock-fs "^4.1.0" + setimmediate "^1.0.5" + tar "^4.0.2" + xhr-request "^1.0.1" + +table-layout@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04" + integrity sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A== + dependencies: + array-back "^4.0.1" + deep-extend "~0.6.0" + typical "^5.2.0" + wordwrapjs "^4.0.0" + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +tape@^4.6.3: + version "4.16.0" + resolved "https://registry.yarnpkg.com/tape/-/tape-4.16.0.tgz#18310f57b71c0ac21b3ef94fe5c16033b3d6362b" + integrity sha512-mBlqYFr2mHysgCFXAuSarIQ+ffhielpb7a5/IbeOhMaLnQYhkJLUm6CwO1RszWeHRxnIpMessZ3xL2Cfo94BWw== + dependencies: + call-bind "~1.0.2" + deep-equal "~1.1.1" + defined "~1.0.0" + dotignore "~0.1.2" + for-each "~0.3.3" + glob "~7.2.3" + has "~1.0.3" + inherits "~2.0.4" + is-regex "~1.1.4" + minimist "~1.2.6" + object-inspect "~1.12.2" + resolve "~1.22.1" + resumer "~0.0.0" + string.prototype.trim "~1.2.6" + through "~2.3.8" + +tar@^4.0.2: + version "4.4.19" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" + integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== + dependencies: + chownr "^1.1.4" + fs-minipass "^1.2.7" + minipass "^2.9.0" + minizlib "^1.3.3" + mkdirp "^0.5.5" + safe-buffer "^5.2.1" + yallist "^3.1.1" + +test-value@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/test-value/-/test-value-2.1.0.tgz#11da6ff670f3471a73b625ca4f3fdcf7bb748291" + integrity sha512-+1epbAxtKeXttkGFMTX9H42oqzOTufR1ceCF+GYA5aOmvaPq9wd4PUS8329fn2RRLGNeUkgRLnVpycjx8DsO2w== + dependencies: + array-back "^1.0.3" + typical "^2.6.0" + +testrpc@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/testrpc/-/testrpc-0.0.1.tgz#83e2195b1f5873aec7be1af8cbe6dcf39edb7aed" + integrity sha512-afH1hO+SQ/VPlmaLUFj2636QMeDvPCeQMc/9RBMW0IfjNe9gFD9Ra3ShqYkB7py0do1ZcCna/9acHyzTJ+GcNA== + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +through2@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +through@^2.3.6, through@^2.3.8, through@~2.3.4, through@~2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +timed-out@^4.0.0, timed-out@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA== + +tmp@0.0.33, tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tmp@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" + integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== + dependencies: + rimraf "^2.6.3" + +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + integrity sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og== + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg== + dependencies: + kind-of "^3.0.2" + +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg== + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + integrity sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw== + +"true-case-path@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-2.2.1.tgz#c5bf04a5bbec3fd118be4084461b3a27c4d796bf" + integrity sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q== + +ts-command-line-args@^2.2.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/ts-command-line-args/-/ts-command-line-args-2.3.1.tgz#b6188e42efc6cf7a8898e438a873fbb15505ddd6" + integrity sha512-FR3y7pLl/fuUNSmnPhfLArGqRrpojQgIEEOVzYx9DhTmfIN7C9RWSfpkJEF4J+Gk7aVx5pak8I7vWZsaN4N84g== + dependencies: + chalk "^4.1.0" + command-line-args "^5.1.1" + command-line-usage "^6.1.0" + string-format "^2.0.0" + +ts-essentials@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-1.0.4.tgz#ce3b5dade5f5d97cf69889c11bf7d2da8555b15a" + integrity sha512-q3N1xS4vZpRouhYHDPwO0bDW3EZ6SK9CrrDHxi/D6BPReSjpVgWIOpLS2o0gSBZm+7q/wyKp6RVM1AeeW7uyfQ== + +ts-essentials@^6.0.3: + version "6.0.7" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-6.0.7.tgz#5f4880911b7581a873783740ce8b94da163d18a6" + integrity sha512-2E4HIIj4tQJlIHuATRHayv0EfMGK3ris/GRk1E3CFnsZzeNV+hUmelbaTZHLtXaZppM5oLhHRtO04gINC4Jusw== + +ts-essentials@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38" + integrity sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ== + +ts-generator@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ts-generator/-/ts-generator-0.1.1.tgz#af46f2fb88a6db1f9785977e9590e7bcd79220ab" + integrity sha512-N+ahhZxTLYu1HNTQetwWcx3so8hcYbkKBHTr4b4/YgObFTIKkOSSsaa+nal12w8mfrJAyzJfETXawbNjSfP2gQ== + dependencies: + "@types/mkdirp" "^0.5.2" + "@types/prettier" "^2.1.1" + "@types/resolve" "^0.0.8" + chalk "^2.4.1" + glob "^7.1.2" + mkdirp "^0.5.1" + prettier "^2.1.2" + resolve "^1.8.1" + ts-essentials "^1.0.0" + +ts-node@^10.4.0: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tslib@^1.9.0, tslib@^1.9.3: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.0, tslib@^2.1.0, tslib@^2.3.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + +tsort@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/tsort/-/tsort-0.0.1.tgz#e2280f5e817f8bf4275657fd0f9aebd44f5a2786" + integrity sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw== + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +tweetnacl-util@^0.15.0, tweetnacl-util@^0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" + integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + +tweetnacl@^1.0.0, tweetnacl@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== + dependencies: + prelude-ls "~1.1.2" + +type-detect@^4.0.0, type-detect@^4.0.5: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" + integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.5.0: + version "2.7.2" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== + +typechain@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/typechain/-/typechain-3.0.0.tgz#d5a47700831f238e43f7429b987b4bb54849b92e" + integrity sha512-ft4KVmiN3zH4JUFu2WJBrwfHeDf772Tt2d8bssDTo/YcckKW2D+OwFrHXRC6hJvO3mHjFQTihoMV6fJOi0Hngg== + dependencies: + command-line-args "^4.0.7" + debug "^4.1.1" + fs-extra "^7.0.0" + js-sha3 "^0.8.0" + lodash "^4.17.15" + ts-essentials "^6.0.3" + ts-generator "^0.1.1" + +typechain@^6.0.5: + version "6.1.0" + resolved "https://registry.yarnpkg.com/typechain/-/typechain-6.1.0.tgz#462a35f555accf870689d1ba5698749108d0ce81" + integrity sha512-GGfkK0p3fUgz8kYxjSS4nKcWXE0Lo+teHTetghousIK5njbNoYNDlwn91QIyD181L3fVqlTvBE0a/q3AZmjNfw== + dependencies: + "@types/prettier" "^2.1.1" + debug "^4.1.1" + fs-extra "^7.0.0" + glob "^7.1.6" + js-sha3 "^0.8.0" + lodash "^4.17.15" + mkdirp "^1.0.4" + prettier "^2.1.2" + ts-command-line-args "^2.2.0" + ts-essentials "^7.0.1" + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== + +typescript@^4.5.4: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== + +typewise-core@^1.2, typewise-core@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/typewise-core/-/typewise-core-1.2.0.tgz#97eb91805c7f55d2f941748fa50d315d991ef195" + integrity sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg== + +typewise@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typewise/-/typewise-1.0.3.tgz#1067936540af97937cc5dcf9922486e9fa284651" + integrity sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ== + dependencies: + typewise-core "^1.2.0" + +typewiselite@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typewiselite/-/typewiselite-1.0.0.tgz#c8882fa1bb1092c06005a97f34ef5c8508e3664e" + integrity sha512-J9alhjVHupW3Wfz6qFRGgQw0N3gr8hOkw6zm7FZ6UR1Cse/oD9/JVok7DNE9TT9IbciDHX2Ex9+ksE6cRmtymw== + +typical@^2.6.0, typical@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d" + integrity sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg== + +typical@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" + integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== + +typical@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" + integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== + +uglify-js@^3.1.4: + version "3.17.0" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.0.tgz#55bd6e9d19ce5eef0d5ad17cd1f587d85b180a85" + integrity sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg== + +ultron@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" + integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +underscore@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" + integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== + +undici@^5.4.0: + version "5.9.1" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.9.1.tgz#fc9fd85dd488f965f153314a63d9426a11f3360b" + integrity sha512-6fB3a+SNnWEm4CJbgo0/CWR8RGcOCQP68SF4X0mxtYTq2VNN8T88NYrWVBAeSX+zb7bny2dx2iYhP3XHi00omg== + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +unorm@^1.3.3: + version "1.6.0" + resolved "https://registry.yarnpkg.com/unorm/-/unorm-1.6.0.tgz#029b289661fba714f1a9af439eb51d9b16c205af" + integrity sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ== + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== + +url-parse-lax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" + integrity sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA== + dependencies: + prepend-http "^1.0.1" + +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ== + dependencies: + prepend-http "^2.0.0" + +url-set-query@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-set-query/-/url-set-query-1.0.0.tgz#016e8cfd7c20ee05cafe7795e892bd0702faa339" + integrity sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg== + +url-to-options@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" + integrity sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A== + +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + integrity sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ== + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +utf-8-validate@^5.0.2: + version "5.0.9" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.9.tgz#ba16a822fbeedff1a58918f2a6a6b36387493ea3" + integrity sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q== + dependencies: + node-gyp-build "^4.3.0" + +utf8@3.0.0, utf8@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" + integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +util.promisify@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.1.1.tgz#77832f57ced2c9478174149cae9b96e9918cd54b" + integrity sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + for-each "^0.3.3" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.1" + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +varint@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/varint/-/varint-5.0.2.tgz#5b47f8a947eb668b848e034dcfa87d0ff8a7f7a4" + integrity sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow== + +vary@^1, vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +web3-bzz@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.2.11.tgz#41bc19a77444bd5365744596d778b811880f707f" + integrity sha512-XGpWUEElGypBjeFyUhTkiPXFbDVD6Nr/S5jznE3t8cWUA0FxRf1n3n/NuIZeb0H9RkN2Ctd/jNma/k8XGa3YKg== + dependencies: + "@types/node" "^12.12.6" + got "9.6.0" + swarm-js "^0.1.40" + underscore "1.9.1" + +web3-core-helpers@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.2.11.tgz#84c681ed0b942c0203f3b324a245a127e8c67a99" + integrity sha512-PEPoAoZd5ME7UfbnCZBdzIerpe74GEvlwT4AjOmHeCVZoIFk7EqvOZDejJHt+feJA6kMVTdd0xzRNN295UhC1A== + dependencies: + underscore "1.9.1" + web3-eth-iban "1.2.11" + web3-utils "1.2.11" + +web3-core-method@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.2.11.tgz#f880137d1507a0124912bf052534f168b8d8fbb6" + integrity sha512-ff0q76Cde94HAxLDZ6DbdmKniYCQVtvuaYh+rtOUMB6kssa5FX0q3vPmixi7NPooFnbKmmZCM6NvXg4IreTPIw== + dependencies: + "@ethersproject/transactions" "^5.0.0-beta.135" + underscore "1.9.1" + web3-core-helpers "1.2.11" + web3-core-promievent "1.2.11" + web3-core-subscriptions "1.2.11" + web3-utils "1.2.11" + +web3-core-promievent@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.2.11.tgz#51fe97ca0ddec2f99bf8c3306a7a8e4b094ea3cf" + integrity sha512-il4McoDa/Ox9Agh4kyfQ8Ak/9ABYpnF8poBLL33R/EnxLsJOGQG2nZhkJa3I067hocrPSjEdlPt/0bHXsln4qA== + dependencies: + eventemitter3 "4.0.4" + +web3-core-requestmanager@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.2.11.tgz#fe6eb603fbaee18530293a91f8cf26d8ae28c45a" + integrity sha512-oFhBtLfOiIbmfl6T6gYjjj9igOvtyxJ+fjS+byRxiwFJyJ5BQOz4/9/17gWR1Cq74paTlI7vDGxYfuvfE/mKvA== + dependencies: + underscore "1.9.1" + web3-core-helpers "1.2.11" + web3-providers-http "1.2.11" + web3-providers-ipc "1.2.11" + web3-providers-ws "1.2.11" + +web3-core-subscriptions@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.2.11.tgz#beca908fbfcb050c16f45f3f0f4c205e8505accd" + integrity sha512-qEF/OVqkCvQ7MPs1JylIZCZkin0aKK9lDxpAtQ1F8niEDGFqn7DT8E/vzbIa0GsOjL2fZjDhWJsaW+BSoAW1gg== + dependencies: + eventemitter3 "4.0.4" + underscore "1.9.1" + web3-core-helpers "1.2.11" + +web3-core@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.2.11.tgz#1043cacc1becb80638453cc5b2a14be9050288a7" + integrity sha512-CN7MEYOY5ryo5iVleIWRE3a3cZqVaLlIbIzDPsvQRUfzYnvzZQRZBm9Mq+ttDi2STOOzc1MKylspz/o3yq/LjQ== + dependencies: + "@types/bn.js" "^4.11.5" + "@types/node" "^12.12.6" + bignumber.js "^9.0.0" + web3-core-helpers "1.2.11" + web3-core-method "1.2.11" + web3-core-requestmanager "1.2.11" + web3-utils "1.2.11" + +web3-eth-abi@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.2.11.tgz#a887494e5d447c2926d557a3834edd66e17af9b0" + integrity sha512-PkRYc0+MjuLSgg03QVWqWlQivJqRwKItKtEpRUaxUAeLE7i/uU39gmzm2keHGcQXo3POXAbOnMqkDvOep89Crg== + dependencies: + "@ethersproject/abi" "5.0.0-beta.153" + underscore "1.9.1" + web3-utils "1.2.11" + +web3-eth-accounts@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.2.11.tgz#a9e3044da442d31903a7ce035a86d8fa33f90520" + integrity sha512-6FwPqEpCfKIh3nSSGeo3uBm2iFSnFJDfwL3oS9pyegRBXNsGRVpgiW63yhNzL0796StsvjHWwQnQHsZNxWAkGw== + dependencies: + crypto-browserify "3.12.0" + eth-lib "0.2.8" + ethereumjs-common "^1.3.2" + ethereumjs-tx "^2.1.1" + scrypt-js "^3.0.1" + underscore "1.9.1" + uuid "3.3.2" + web3-core "1.2.11" + web3-core-helpers "1.2.11" + web3-core-method "1.2.11" + web3-utils "1.2.11" + +web3-eth-contract@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.2.11.tgz#917065902bc27ce89da9a1da26e62ef663663b90" + integrity sha512-MzYuI/Rq2o6gn7vCGcnQgco63isPNK5lMAan2E51AJLknjSLnOxwNY3gM8BcKoy4Z+v5Dv00a03Xuk78JowFow== + dependencies: + "@types/bn.js" "^4.11.5" + underscore "1.9.1" + web3-core "1.2.11" + web3-core-helpers "1.2.11" + web3-core-method "1.2.11" + web3-core-promievent "1.2.11" + web3-core-subscriptions "1.2.11" + web3-eth-abi "1.2.11" + web3-utils "1.2.11" + +web3-eth-ens@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.2.11.tgz#26d4d7f16d6cbcfff918e39832b939edc3162532" + integrity sha512-dbW7dXP6HqT1EAPvnniZVnmw6TmQEKF6/1KgAxbo8iBBYrVTMDGFQUUnZ+C4VETGrwwaqtX4L9d/FrQhZ6SUiA== + dependencies: + content-hash "^2.5.2" + eth-ens-namehash "2.0.8" + underscore "1.9.1" + web3-core "1.2.11" + web3-core-helpers "1.2.11" + web3-core-promievent "1.2.11" + web3-eth-abi "1.2.11" + web3-eth-contract "1.2.11" + web3-utils "1.2.11" + +web3-eth-iban@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.2.11.tgz#f5f73298305bc7392e2f188bf38a7362b42144ef" + integrity sha512-ozuVlZ5jwFC2hJY4+fH9pIcuH1xP0HEFhtWsR69u9uDIANHLPQQtWYmdj7xQ3p2YT4bQLq/axKhZi7EZVetmxQ== + dependencies: + bn.js "^4.11.9" + web3-utils "1.2.11" + +web3-eth-personal@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.2.11.tgz#a38b3942a1d87a62070ce0622a941553c3d5aa70" + integrity sha512-42IzUtKq9iHZ8K9VN0vAI50iSU9tOA1V7XU2BhF/tb7We2iKBVdkley2fg26TxlOcKNEHm7o6HRtiiFsVK4Ifw== + dependencies: + "@types/node" "^12.12.6" + web3-core "1.2.11" + web3-core-helpers "1.2.11" + web3-core-method "1.2.11" + web3-net "1.2.11" + web3-utils "1.2.11" + +web3-eth@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.2.11.tgz#4c81fcb6285b8caf544058fba3ae802968fdc793" + integrity sha512-REvxW1wJ58AgHPcXPJOL49d1K/dPmuw4LjPLBPStOVkQjzDTVmJEIsiLwn2YeuNDd4pfakBwT8L3bz1G1/wVsQ== + dependencies: + underscore "1.9.1" + web3-core "1.2.11" + web3-core-helpers "1.2.11" + web3-core-method "1.2.11" + web3-core-subscriptions "1.2.11" + web3-eth-abi "1.2.11" + web3-eth-accounts "1.2.11" + web3-eth-contract "1.2.11" + web3-eth-ens "1.2.11" + web3-eth-iban "1.2.11" + web3-eth-personal "1.2.11" + web3-net "1.2.11" + web3-utils "1.2.11" + +web3-net@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.2.11.tgz#eda68ef25e5cdb64c96c39085cdb74669aabbe1b" + integrity sha512-sjrSDj0pTfZouR5BSTItCuZ5K/oZPVdVciPQ6981PPPIwJJkCMeVjD7I4zO3qDPCnBjBSbWvVnLdwqUBPtHxyg== + dependencies: + web3-core "1.2.11" + web3-core-method "1.2.11" + web3-utils "1.2.11" + +web3-provider-engine@14.2.1: + version "14.2.1" + resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-14.2.1.tgz#ef351578797bf170e08d529cb5b02f8751329b95" + integrity sha512-iSv31h2qXkr9vrL6UZDm4leZMc32SjWJFGOp/D92JXfcEboCqraZyuExDkpxKw8ziTufXieNM7LSXNHzszYdJw== + dependencies: + async "^2.5.0" + backoff "^2.5.0" + clone "^2.0.0" + cross-fetch "^2.1.0" + eth-block-tracker "^3.0.0" + eth-json-rpc-infura "^3.1.0" + eth-sig-util "^1.4.2" + ethereumjs-block "^1.2.2" + ethereumjs-tx "^1.2.0" + ethereumjs-util "^5.1.5" + ethereumjs-vm "^2.3.4" + json-rpc-error "^2.0.0" + json-stable-stringify "^1.0.1" + promise-to-callback "^1.0.0" + readable-stream "^2.2.9" + request "^2.85.0" + semaphore "^1.0.3" + ws "^5.1.1" + xhr "^2.2.0" + xtend "^4.0.1" + +web3-providers-http@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.2.11.tgz#1cd03442c61670572d40e4dcdf1faff8bd91e7c6" + integrity sha512-psh4hYGb1+ijWywfwpB2cvvOIMISlR44F/rJtYkRmQ5jMvG4FOCPlQJPiHQZo+2cc3HbktvvSJzIhkWQJdmvrA== + dependencies: + web3-core-helpers "1.2.11" + xhr2-cookies "1.1.0" + +web3-providers-ipc@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.2.11.tgz#d16d6c9be1be6e0b4f4536c4acc16b0f4f27ef21" + integrity sha512-yhc7Y/k8hBV/KlELxynWjJDzmgDEDjIjBzXK+e0rHBsYEhdCNdIH5Psa456c+l0qTEU2YzycF8VAjYpWfPnBpQ== + dependencies: + oboe "2.1.4" + underscore "1.9.1" + web3-core-helpers "1.2.11" + +web3-providers-ws@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.2.11.tgz#a1dfd6d9778d840561d9ec13dd453046451a96bb" + integrity sha512-ZxnjIY1Er8Ty+cE4migzr43zA/+72AF1myzsLaU5eVgdsfV7Jqx7Dix1hbevNZDKFlSoEyq/3j/jYalh3So1Zg== + dependencies: + eventemitter3 "4.0.4" + underscore "1.9.1" + web3-core-helpers "1.2.11" + websocket "^1.0.31" + +web3-shh@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.2.11.tgz#f5d086f9621c9a47e98d438010385b5f059fd88f" + integrity sha512-B3OrO3oG1L+bv3E1sTwCx66injW1A8hhwpknDUbV+sw3fehFazA06z9SGXUefuFI1kVs4q2vRi0n4oCcI4dZDg== + dependencies: + web3-core "1.2.11" + web3-core-method "1.2.11" + web3-core-subscriptions "1.2.11" + web3-net "1.2.11" + +web3-utils@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.11.tgz#af1942aead3fb166ae851a985bed8ef2c2d95a82" + integrity sha512-3Tq09izhD+ThqHEaWYX4VOT7dNPdZiO+c/1QMA0s5X2lDFKK/xHJb7cyTRRVzN2LvlHbR7baS1tmQhSua51TcQ== + dependencies: + bn.js "^4.11.9" + eth-lib "0.2.8" + ethereum-bloom-filters "^1.0.6" + ethjs-unit "0.1.6" + number-to-bn "1.7.0" + randombytes "^2.1.0" + underscore "1.9.1" + utf8 "3.0.0" + +web3-utils@^1.0.0-beta.31: + version "1.7.5" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.7.5.tgz#081a952ac6e0322e25ac97b37358a43c7372ef6a" + integrity sha512-9AqNOziQky4wNQadEwEfHiBdOZqopIHzQQVzmvvv6fJwDSMhP+khqmAZC7YTiGjs0MboyZ8tWNivqSO1699XQw== + dependencies: + bn.js "^5.2.1" + ethereum-bloom-filters "^1.0.6" + ethereumjs-util "^7.1.0" + ethjs-unit "0.1.6" + number-to-bn "1.7.0" + randombytes "^2.1.0" + utf8 "3.0.0" + +web3@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3/-/web3-1.2.11.tgz#50f458b2e8b11aa37302071c170ed61cff332975" + integrity sha512-mjQ8HeU41G6hgOYm1pmeH0mRAeNKJGnJEUzDMoerkpw7QUQT4exVREgF1MYPvL/z6vAshOXei25LE/t/Bxl8yQ== + dependencies: + web3-bzz "1.2.11" + web3-core "1.2.11" + web3-eth "1.2.11" + web3-eth-personal "1.2.11" + web3-net "1.2.11" + web3-shh "1.2.11" + web3-utils "1.2.11" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +websocket@1.0.32: + version "1.0.32" + resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.32.tgz#1f16ddab3a21a2d929dec1687ab21cfdc6d3dbb1" + integrity sha512-i4yhcllSP4wrpoPMU2N0TQ/q0O94LRG/eUQjEAamRltjQ1oT1PFFKOG4i877OlJgCG8rw6LrrowJp+TYCEWF7Q== + dependencies: + bufferutil "^4.0.1" + debug "^2.2.0" + es5-ext "^0.10.50" + typedarray-to-buffer "^3.1.5" + utf-8-validate "^5.0.2" + yaeti "^0.0.6" + +websocket@^1.0.31: + version "1.0.34" + resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111" + integrity sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ== + dependencies: + bufferutil "^4.0.1" + debug "^2.2.0" + es5-ext "^0.10.50" + typedarray-to-buffer "^3.1.5" + utf-8-validate "^5.0.2" + yaeti "^0.0.6" + +whatwg-fetch@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" + integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + integrity sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ== + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" + +window-size@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" + integrity sha512-UD7d8HFA2+PZsbKyaOCEy8gMh1oDtHgJh1LfgjQ4zVXmYjAT/kvz3PueITKuqDiIXQe7yzpPnxX3lNc+AhQMyw== + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + +wordwrapjs@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.1.tgz#d9790bccfb110a0fc7836b5ebce0937b37a8b98f" + integrity sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA== + dependencies: + reduce-flatten "^2.0.0" + typical "^5.2.0" + +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw== + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +ws@7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + +ws@^3.0.0: + version "3.3.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" + integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== + dependencies: + async-limiter "~1.0.0" + safe-buffer "~5.1.0" + ultron "~1.1.0" + +ws@^5.1.1: + version "5.2.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.3.tgz#05541053414921bc29c63bee14b8b0dd50b07b3d" + integrity sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA== + dependencies: + async-limiter "~1.0.0" + +ws@^7.4.6: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + +xhr-request-promise@^0.1.2: + version "0.1.3" + resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz#2d5f4b16d8c6c893be97f1a62b0ed4cf3ca5f96c" + integrity sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg== + dependencies: + xhr-request "^1.1.0" + +xhr-request@^1.0.1, xhr-request@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/xhr-request/-/xhr-request-1.1.0.tgz#f4a7c1868b9f198723444d82dcae317643f2e2ed" + integrity sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA== + dependencies: + buffer-to-arraybuffer "^0.0.5" + object-assign "^4.1.1" + query-string "^5.0.1" + simple-get "^2.7.0" + timed-out "^4.0.1" + url-set-query "^1.0.0" + xhr "^2.0.4" + +xhr2-cookies@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz#7d77449d0999197f155cb73b23df72505ed89d48" + integrity sha512-hjXUA6q+jl/bd8ADHcVfFsSPIf+tyLIjuO9TwJC9WI6JP2zKcS7C+p56I9kCLLsaCiNT035iYvEUUzdEFj/8+g== + dependencies: + cookiejar "^2.1.1" + +xhr@^2.0.4, xhr@^2.2.0, xhr@^2.3.3: + version "2.6.0" + resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.6.0.tgz#b69d4395e792b4173d6b7df077f0fc5e4e2b249d" + integrity sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA== + dependencies: + global "~4.4.0" + is-function "^1.0.1" + parse-headers "^2.0.0" + xtend "^4.0.0" + +xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +xtend@~2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b" + integrity sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ== + dependencies: + object-keys "~0.4.0" + +y18n@^3.2.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" + integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yaeti@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" + integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug== + +yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.1.1.tgz#1e06fb4ca46e60d9da07e4f786ea370ed3c3cfec" + integrity sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw== + +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + +yargs-parser@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4" + integrity sha512-9pIKIJhnI5tonzG6OnCFlz/yln8xHYcGl+pn3xR0Vzff0vzN1PbNRaelgfgRUwZ3s4i3jvxT9WhmUGL4whnasA== + dependencies: + camelcase "^3.0.0" + lodash.assign "^4.0.6" + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-unparser@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + +yargs@16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^4.7.1: + version "4.8.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" + integrity sha512-LqodLrnIDM3IFT+Hf/5sxBnEGECrfdC1uIbgZeJmESCSo4HoCAaKEus8MylXHAkdacGc0ye+Qa+dpkuom8uVYA== + dependencies: + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + lodash.assign "^4.0.3" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.1" + which-module "^1.0.0" + window-size "^0.2.0" + y18n "^3.2.1" + yargs-parser "^2.4.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 92ab45d6fa543cb8666944ff0cb117ffa50fa3db Mon Sep 17 00:00:00 2001 From: nxqbao Date: Fri, 19 Aug 2022 13:29:20 +0700 Subject: [PATCH 002/190] Add interface of ValidatorSet and Slash --- contracts/interfaces/ISlashIndicator.sol | 57 ++++++++++++++ contracts/interfaces/IValidatorSet.sol | 96 ++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 contracts/interfaces/ISlashIndicator.sol create mode 100644 contracts/interfaces/IValidatorSet.sol diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol new file mode 100644 index 000000000..1ba4c7efb --- /dev/null +++ b/contracts/interfaces/ISlashIndicator.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface ISlashIndicator { + + struct Indicator { + /// @dev The block height that the indicator get updated, make sure this update once each block + uint256 height; + + /// @dev Number of missed block the validator, should not be get decreased to keep track the + /// misbehavior records the validator. + uint256 counter; + } + + /** + * @notice Slash for inavailability + * + * @dev Increase the counter of validator with valAddr. If the counter passes the threshold, call + * the function from Validators.sol + * + * Requirements: + * - Only coinbase can call this method + * + */ + function slash(address valAddr) external; + + /** + * @dev Reset the counter of the validator everyday + * + * Requirements: + * - Only validator can call this method + */ + function resetCounter() external; + + /** + * @notice Slash for double signing + * + * @dev Verify the evidence, call the function from Validators.sol + * + * Requirements: + * - Only coinbase can call this method + * + */ + function slashDoubleSign (address valAddr, bytes calldata evidence) external; + + + /// + /// QUERY FUNCTIONS + /// + + /** + * @notice Get slash indicator of a validator + */ + function getSlashIndicator(address validator) external view returns (uint256 height, uint256 counter); +} + diff --git a/contracts/interfaces/IValidatorSet.sol b/contracts/interfaces/IValidatorSet.sol new file mode 100644 index 000000000..9fca120d1 --- /dev/null +++ b/contracts/interfaces/IValidatorSet.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +/** + * @title Set of validators in current epoch + * @notice This contract maintains set of validator in the current epoch of Ronin network + */ +interface IValidatorSet { + + struct Validator { + /// @dev Address of the validator that produces block, e.g. block.coinbase + address consensusAddress; + + /// @dev Address that receives mining fee of the validator + address payable feeAddress; + + /// @dev Total reward amount + uint256 totalAmount; + + /// @dev Remaining time being in jail (not able to produce block) + uint256 jailTime; + + /// @dev + uint256 reward; + + /// @dev For upgrability purpose + /// @custom:note Consider leave or keep this attribute + uint256[20] ___gap; + } + + /** + * @notice The block producer update the validator set at the end of each epoch. + * + * @dev This method is called at the end of each epoch by comparing on `block.number`. The next + * validator set is not sent by the validator, but is fetched and calculated from `Stake.sol` + * contract. The order in the set of the validator is also the order to mining block in the next + * epoch. Voting power (VP) of each validator only affects their shared mining reward. + * + * @custom:note Consider only allowing coinbase to call this method + */ + function updateValidators() external returns(address[] memory); + + /** + * @notice The block producers call this method to send the mining reward in coinbase transaction. + * + * @dev Requirements: + * - Only coinbase address can call this method + **/ + function depositReward() external payable; + + /** + * @notice Slash the validator that missed 50 block a day + * + * @dev Requirements: + * - Only slash contract can call this method + */ + function slashMisdemeanor(address validator) external; + + /** + * @notice Slash the validator that missed 150 block a day + * + * @dev Requirements: + * - Only slash contract can call this method + */ + function slashFelony(address validator) external; + + /** + * @notice Slash the validator that created 2 blocks on a same height + * + * @dev Requirements: + * - Only slash contract can call this method + */ + function slashDoubleSign(address validator) external; + + + /// + /// QUERY FUNCTIONS + /// + + /** + * @notice All validators call this method to get the set of validators in current epoch. + * Primarily, this method will be called at the beginning of each epoch. + */ + function getValidators() external view returns(address[] memory); + + /** + * @notice Check if an address is in the validator list of the current epoch + */ + function isCurrentValidator(address validator) external view returns (bool); + + /** + * @notice Return last block height when the set of validators is updated + */ + function getLastUpdated() external view returns (uint256 height); +} From 09db9511dc3936841106c5ecef58afd6aeec4035 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Fri, 19 Aug 2022 16:27:06 +0700 Subject: [PATCH 003/190] Add quick sort lib --- contracts/libraries/QuickSort.sol | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 contracts/libraries/QuickSort.sol diff --git a/contracts/libraries/QuickSort.sol b/contracts/libraries/QuickSort.sol new file mode 100644 index 000000000..ab42c8550 --- /dev/null +++ b/contracts/libraries/QuickSort.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +library QuickSort { + function sort(uint256[] memory data) public view returns (uint256[] memory) { + return _quickSort(data, int256(0), int256(data.length - 1)); + } + + function _quickSort( + uint256[] memory arr, + int256 left, + int256 right + ) view private returns (uint256[] memory) { + int256 i = left; + int256 j = right; + if (i == j) return arr; + uint256 pivot = arr[uint256(left + (right - left) / 2)]; + while (i <= j) { + while (arr[uint256(i)] < pivot) i++; + while (pivot < arr[uint256(j)]) j--; + if (i <= j) { + (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]); + i++; + j--; + } + } + if (left < j) _quickSort(arr, left, j); + if (i < right) _quickSort(arr, i, right); + + return arr; + } +} From c79432155537498322269c1a09cf1a2ccb6484db Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 22 Aug 2022 12:03:20 +0700 Subject: [PATCH 004/190] Add linter --- .husky/.gitignore | 1 + .husky/pre-commit | 9 +++++++ contracts/interfaces/ISlashIndicator.sol | 20 ++++++--------- contracts/interfaces/IValidatorSet.sol | 31 +++++++++--------------- 4 files changed, 30 insertions(+), 31 deletions(-) create mode 100644 .husky/.gitignore create mode 100755 .husky/pre-commit diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 000000000..31354ec13 --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..6af89dfdf --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,9 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +set -ex + +yarn lint-staged +yarn compile +yarn docgen +git add docs diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index 1ba4c7efb..a64e148af 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -3,22 +3,20 @@ pragma solidity ^0.8.9; interface ISlashIndicator { - - struct Indicator { + struct Indicator { /// @dev The block height that the indicator get updated, make sure this update once each block - uint256 height; - - /// @dev Number of missed block the validator, should not be get decreased to keep track the - /// misbehavior records the validator. + uint256 height; + /// @dev Number of missed block the validator, should not be get decreased to keep track the + /// misbehavior records the validator. uint256 counter; } /** * @notice Slash for inavailability * - * @dev Increase the counter of validator with valAddr. If the counter passes the threshold, call + * @dev Increase the counter of validator with valAddr. If the counter passes the threshold, call * the function from Validators.sol - * + * * Requirements: * - Only coinbase can call this method * @@ -40,10 +38,9 @@ interface ISlashIndicator { * * Requirements: * - Only coinbase can call this method - * + * */ - function slashDoubleSign (address valAddr, bytes calldata evidence) external; - + function slashDoubleSign(address valAddr, bytes calldata evidence) external; /// /// QUERY FUNCTIONS @@ -54,4 +51,3 @@ interface ISlashIndicator { */ function getSlashIndicator(address validator) external view returns (uint256 height, uint256 counter); } - diff --git a/contracts/interfaces/IValidatorSet.sol b/contracts/interfaces/IValidatorSet.sol index 9fca120d1..f933be5f8 100644 --- a/contracts/interfaces/IValidatorSet.sol +++ b/contracts/interfaces/IValidatorSet.sol @@ -7,24 +7,18 @@ pragma solidity ^0.8.9; * @notice This contract maintains set of validator in the current epoch of Ronin network */ interface IValidatorSet { - struct Validator { /// @dev Address of the validator that produces block, e.g. block.coinbase address consensusAddress; - /// @dev Address that receives mining fee of the validator address payable feeAddress; - /// @dev Total reward amount uint256 totalAmount; - /// @dev Remaining time being in jail (not able to produce block) uint256 jailTime; - - /// @dev + /// @dev uint256 reward; - - /// @dev For upgrability purpose + /// @dev For upgrability purpose /// @custom:note Consider leave or keep this attribute uint256[20] ___gap; } @@ -33,13 +27,13 @@ interface IValidatorSet { * @notice The block producer update the validator set at the end of each epoch. * * @dev This method is called at the end of each epoch by comparing on `block.number`. The next - * validator set is not sent by the validator, but is fetched and calculated from `Stake.sol` + * validator set is not sent by the validator, but is fetched and calculated from `Stake.sol` * contract. The order in the set of the validator is also the order to mining block in the next * epoch. Voting power (VP) of each validator only affects their shared mining reward. * * @custom:note Consider only allowing coinbase to call this method */ - function updateValidators() external returns(address[] memory); + function updateValidators() external returns (address[] memory); /** * @notice The block producers call this method to send the mining reward in coinbase transaction. @@ -48,10 +42,10 @@ interface IValidatorSet { * - Only coinbase address can call this method **/ function depositReward() external payable; - + /** * @notice Slash the validator that missed 50 block a day - * + * * @dev Requirements: * - Only slash contract can call this method */ @@ -59,21 +53,20 @@ interface IValidatorSet { /** * @notice Slash the validator that missed 150 block a day - * + * * @dev Requirements: * - Only slash contract can call this method - */ + */ function slashFelony(address validator) external; /** * @notice Slash the validator that created 2 blocks on a same height - * + * * @dev Requirements: * - Only slash contract can call this method - */ + */ function slashDoubleSign(address validator) external; - /// /// QUERY FUNCTIONS /// @@ -82,7 +75,7 @@ interface IValidatorSet { * @notice All validators call this method to get the set of validators in current epoch. * Primarily, this method will be called at the beginning of each epoch. */ - function getValidators() external view returns(address[] memory); + function getValidators() external view returns (address[] memory); /** * @notice Check if an address is in the validator list of the current epoch @@ -90,7 +83,7 @@ interface IValidatorSet { function isCurrentValidator(address validator) external view returns (bool); /** - * @notice Return last block height when the set of validators is updated + * @notice Return last block height when the set of validators is updated */ function getLastUpdated() external view returns (uint256 height); } From a741c842e093685d70feecf4a25711043b755219 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 22 Aug 2022 18:03:15 +0700 Subject: [PATCH 005/190] Init commit staking contract --- contracts/Staking.sol | 157 ++++++++++++++++++++++++++++++ contracts/interfaces/IStaking.sol | 123 +++++++++++++++++++++++ 2 files changed, 280 insertions(+) create mode 100644 contracts/Staking.sol create mode 100644 contracts/interfaces/IStaking.sol diff --git a/contracts/Staking.sol b/contracts/Staking.sol new file mode 100644 index 000000000..ec08eba4c --- /dev/null +++ b/contracts/Staking.sol @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "./interfaces/IStaking.sol"; + +contract Staking is IStaking, Initializable { + mapping(address => uint256) claimableReward; + mapping(address => uint256) pendingReward; + + /// @dev (consensusAddress => validator index) in `validators` + mapping(address => uint256) public validatorIndexes; + ValidatorInfo[] public validators; + + mapping(address => DelegatorInfo) delegators; + + /// @dev Configuration of maximum number of validator + uint256 validatorThreshold; + /// @dev Configuration of number of blocks that validator has to wait before unstaking, counted from staking time + uint256 unstakingOnHoldBlocksNum; + /// @dev Configuration of minimum balance for being a validator + uint256 minValidatorBalance; + + event ProposedValidator(address indexed consensusAddr, address indexed stakingAddr, uint256 amount); + event Staked(address indexed validator, uint256 amount); + event Unstaked(address indexed validator, uint256 amount); + event Delegated(address indexed delegator, address indexed validator, uint256 amount); + event Undelegated(address indexed delegator, address indexed validator, uint256 amount); + + constructor() { + _disableInitializers(); + } + + function initialize() external initializer { + validatorThreshold = 50; + unstakingOnHoldBlocksNum = 28800; // 28800 blocks ~= 1 day + minValidatorBalance = 1e25; // 10M RON + + /// Add empty validator at 0-index + validators.push(); + } + + /// + /// VALIDATOR FUNCTIONS + /// + + function proposeValidator( + address _consensusAddr, + address payable _feeAddr, + uint256 _commissionRate, + uint256 _amount + ) external payable returns (uint256 index_) { + require(validators.length < validatorThreshold, "Validators threshold exceeded"); + require(msg.value == _amount, "Transfer RON failed"); + require(!_existValidator(_consensusAddr), "Validator existed"); + + (uint256 index, ValidatorInfo storage _currValidator) = _newValidator(_consensusAddr); + + _currValidator.consensusAddress = _consensusAddr; + _currValidator.stakingAddress = msg.sender; + _currValidator.feeAddress = _feeAddr; + _currValidator.staked = _amount; + _currValidator.balance = _amount; + _currValidator.lastStakingBlock = block.number; + + emit ProposedValidator(_consensusAddr, msg.sender, _amount); + + return index; + } + + function stake(address _consensusAddress, uint256 _amount) external payable { + require(msg.value == _amount, "Transfer RON failed"); + + ValidatorInfo storage currValidator = EnumerableMapValidatorInfo._getValidator(_consensusAddress); + currValidator.staked += _amount; + currValidator.stakedDiff += int256(_amount); + currValidator.lastStakingBlock = block.number; + + emit Staked(_consensusAddress, _amount); + } + + function unstake(address _consensusAddress, uint256 _amount) external { + ValidatorInfo storage _currValidator = _getValidator(_consensusAddress); + require(msg.sender == _currValidator.stakingAddress, "Caller must be the staker"); + require(block.number >= _currValidator.lastStakingBlock + unstakingOnHoldBlocksNum, "Staking is on hold period"); + + uint256 remainingBalance = _currValidator.staked - _amount; + require(remainingBalance >= minValidatorBalance || remainingBalance == 0, "Invalid unstaking amount"); + + _currValidator.stakedDiff -= int256(_amount); + _currValidator.balance = (_currValidator.staked == 0) ? 0 : _currValidator.balance - _amount; + + emit Unstaked(_currValidator.consensusAddress, _amount); + } + + /// + /// DELEGATOR FUNCTIONS + /// + + function delegate(address _validatorAddr, uint256 _amount) external payable { + require(msg.value == _amount, "Transfer RON failed"); + + DelegatorInfo storage _currDelegator = delegators[msg.sender]; + _currDelegator.balance += _amount; + _currDelegator.delegatedOfValidator[_validatorAddr] += _amount; + + ValidatorInfo storage _currValidator = _getValidator(_validatorAddr); + _currValidator.stakedDiff += int256(_amount); + + emit Delegated(msg.sender, _validatorAddr, _amount); + } + + function undelegate(address _validatorAddr, uint256 _amount) external payable { + DelegatorInfo storage _currDelegator = delegators[msg.sender]; + require (_currDelegator.delegatedOfValidator[_validatorAddr] >= _amount, "Invalid undelegating amount"); + + _currDelegator.balance -= _amount; + _currDelegator.delegatedOfValidator[_validatorAddr] -= _amount; + + ValidatorInfo storage _currValidator = _getValidator(_validatorAddr); + _currValidator.stakedDiff -= int256(_amount); + + emit Undelegated(msg.sender, _validatorAddr, _amount); + } + + /// + /// HELPER ON VALIDATOR MAPPING FUNCTIONS + /// + + function _existValidator(address _consensusAddr) private view returns (bool) { + uint256 _index = validatorIndexes[_consensusAddr]; + return (_index != 0); + } + + function _getValidator(address _consensusAddr) private view returns (ValidatorInfo storage) { + (bool success, ValidatorInfo storage currValidator) = _tryGetValidator(_consensusAddr); + require(success, "Nonexistent validator"); + return currValidator; + } + + function _tryGetValidator(address _consensusAddr) private view returns (bool, ValidatorInfo storage) { + uint256 _index = validatorIndexes[_consensusAddr]; + ValidatorInfo storage _currValidator = validators[_index]; + + if (_index == 0) return (false, _currValidator); + + return (true, _currValidator); + } + + function _newValidator(address _consensusAddr) private returns (uint256, ValidatorInfo storage) { + uint256 index = validators.length; + validatorIndexes[_consensusAddr] = index; + return (index, validators.push()); + } + +} diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol new file mode 100644 index 000000000..820b4d38b --- /dev/null +++ b/contracts/interfaces/IStaking.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IStaking { + struct ValidatorInfo { + /// @dev Address of the validator that produces block, e.g. block.coinbase. This is so-called validator address. + address consensusAddress; + /// @dev Address that stakes for the validator, each consensus address has only one staking address + address stakingAddress; + /// @dev Address that receives mining fee of the validator + address payable feeAddress; + /// @dev Total reward amount + uint256 totalReward; + /// @dev The percentile of reward that delegators can be received + uint256 commissionRate; + /// @dev The amount of staked coin in the previous epoch + uint256 staked; + /// @dev The difference between the amount of staked coin in the previous epoch and the current epoch + int256 stakedDiff; + /// @dev Sum of staked amount from validator and delegators + uint256 balance; + /// @dev Last staking block number, used for calculating time condition of unstaking + uint256 lastStakingBlock; + /// @dev For upgrability purpose + uint256[20] ____gap; + } + + struct DelegatorInfo { + /// @dev Total amount of delegated token + uint256 balance; + uint256 totalReward; + uint256 pendingReward; + /// @dev Delegating amount of delegator for each validator (validator consensus address => amount) + mapping(address => uint256) delegatedOfValidator; + /// @dev For upgrability purpose + uint256[20] ____gap; + } + + /** + * @notice First proposing a validator with information, require to stake at least `MINIMUM_STAKING` amount + * + * @return index The index of new validator in the validator list + */ + function proposeValidator( + address consensusAddress, + address payable feeAddress, + uint256 commissionRate, + uint256 amount + ) external payable returns (uint256 index); + + /** + * @notice Stake as validator. + */ + function stake(address consensusAddress, uint256 amount) external payable; + + /** + * @notice Unstake as validator. Need to wait `UNSTAKING_ON_HOLD_BLOCKS_NUM` blocks. + * @dev The remain balance must either be greater than `MINIMUM_STAKING` value or equal to zero. + */ + function unstake(address consensusAddress, uint256 amount) external; + + /** + * @notice Stake as delegator for a validator + * + * @dev Each delegator can stake token to many validators. Upon each delegation, the following + * actions are done: + * - Update staking balance of delegator for corresponding validator + * - Update staking balance of delegator + * - Not update `balance` of validator + * - Update `stakedDiff` of validator to record and update into `balance` at the end of each + * epoch in `updateValidatorSet()` + */ + function delegate(address validatorAddress, uint256 amount) external payable; + + /** + * @notice Unstake as delegator for a validator + */ + function undelegate(address user, uint256 amount) external; + + /** + * @notice Update set of validators + * + * @dev Add stake and stake_diff for each validator. Obtain currentValidatorSet based on validators + * + * Requirements: + * - Only validator and `ValidatorSet` contract can call this function + */ + function updateValidatorSet() external; + + /** + * @notice Distribute reward + * + * @dev Allocate reward based on staked coins of delegators pendingReward. + * + * Requirements: + * - Only validator can call this function + */ + function allocateReward(address valAddr, uint256 amount) external; + + /** + * @notice Distribute reward + * + * @dev Get the reward from Validator.sol contract. Update pendingReward to claimableReward. + * + * Requirements: + * - Only validator can call this function + */ + function receiveReward(address valAddr) external payable; + + /** + * @notice Delegators call this method to claim their pending rewards + */ + function claimReward(uint256 amount) external; + + /** + * @notice Reduce amount stake from the validator. + * + * Requirements: + * - Only validators or Validator contract can call this function + */ + function slash(address valAddr, uint256 amount) external; +} From bbffbfd3164618217da6fe5bc6241700caf4bc75 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 23 Aug 2022 10:36:40 +0700 Subject: [PATCH 006/190] update --- contracts/Staking.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/Staking.sol b/contracts/Staking.sol index ec08eba4c..23529dc45 100644 --- a/contracts/Staking.sol +++ b/contracts/Staking.sol @@ -72,7 +72,7 @@ contract Staking is IStaking, Initializable { function stake(address _consensusAddress, uint256 _amount) external payable { require(msg.value == _amount, "Transfer RON failed"); - ValidatorInfo storage currValidator = EnumerableMapValidatorInfo._getValidator(_consensusAddress); + ValidatorInfo storage currValidator = _getValidator(_consensusAddress); currValidator.staked += _amount; currValidator.stakedDiff += int256(_amount); currValidator.lastStakingBlock = block.number; @@ -111,7 +111,7 @@ contract Staking is IStaking, Initializable { emit Delegated(msg.sender, _validatorAddr, _amount); } - function undelegate(address _validatorAddr, uint256 _amount) external payable { + function undelegate(address _validatorAddr, uint256 _amount) external { DelegatorInfo storage _currDelegator = delegators[msg.sender]; require (_currDelegator.delegatedOfValidator[_validatorAddr] >= _amount, "Invalid undelegating amount"); From 3fd308fcf29b73d41cba93e63e4ef72707c0deda Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 23 Aug 2022 10:40:17 +0700 Subject: [PATCH 007/190] Update --- contracts/interfaces/IStaking.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 820b4d38b..a7d2754ee 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -12,7 +12,7 @@ interface IStaking { address payable feeAddress; /// @dev Total reward amount uint256 totalReward; - /// @dev The percentile of reward that delegators can be received + /// @dev The percentile of reward that validators can be received, the rest goes to the delegators uint256 commissionRate; /// @dev The amount of staked coin in the previous epoch uint256 staked; From b053aaf8416966f9babf1548d0f57c6b59fe66fe Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 23 Aug 2022 17:29:29 +0700 Subject: [PATCH 008/190] Update deps & fix lint --- .husky/pre-commit | 2 -- contracts/libraries/QuickSort.sol | 2 +- hardhat.config.ts | 2 +- package.json | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 6af89dfdf..a77b1e660 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -5,5 +5,3 @@ set -ex yarn lint-staged yarn compile -yarn docgen -git add docs diff --git a/contracts/libraries/QuickSort.sol b/contracts/libraries/QuickSort.sol index ab42c8550..fe841118a 100644 --- a/contracts/libraries/QuickSort.sol +++ b/contracts/libraries/QuickSort.sol @@ -11,7 +11,7 @@ library QuickSort { uint256[] memory arr, int256 left, int256 right - ) view private returns (uint256[] memory) { + ) private view returns (uint256[] memory) { int256 i = left; int256 j = right; if (i == j) return arr; diff --git a/hardhat.config.ts b/hardhat.config.ts index 7519e7898..116ea5b61 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -1,7 +1,7 @@ import '@typechain/hardhat'; import '@nomiclabs/hardhat-waffle'; import '@nomiclabs/hardhat-ethers'; -import '@axieinfinity/hardhat-deploy'; +import 'hardhat-deploy'; import * as dotenv from 'dotenv'; import { HardhatUserConfig, NetworkUserConfig, SolcUserConfig } from 'hardhat/types'; diff --git a/package.json b/package.json index 876454f2d..5b2be75c5 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,6 @@ "typescript": "^4.5.4" }, "engines": { - "node": ">=12" + "node": ">=14" } } From 5ea4ba0a404649c247cf0389cfd0d3965861ca1c Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 23 Aug 2022 17:30:15 +0700 Subject: [PATCH 009/190] Update staking contract & its interface --- contracts/Staking.sol | 276 +++++++++++++++---------- contracts/interfaces/IStaking.sol | 69 ++++--- contracts/interfaces/IValidatorSet.sol | 2 +- 3 files changed, 205 insertions(+), 142 deletions(-) diff --git a/contracts/Staking.sol b/contracts/Staking.sol index 23529dc45..48c561c42 100644 --- a/contracts/Staking.sol +++ b/contracts/Staking.sol @@ -5,153 +5,211 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "./interfaces/IStaking.sol"; -contract Staking is IStaking, Initializable { - mapping(address => uint256) claimableReward; - mapping(address => uint256) pendingReward; - - /// @dev (consensusAddress => validator index) in `validators` - mapping(address => uint256) public validatorIndexes; - ValidatorInfo[] public validators; - - mapping(address => DelegatorInfo) delegators; - +abstract contract Staking is IStaking, Initializable { + /// TODO: expose fn to get validator info by validator address. + /// @dev Mapping from consensus address => validator index in `validatorCandidates`. + mapping(address => uint256) internal _validatorIndexes; + /// TODO: expose fn returns the whole validator arry. + /// @dev Validator array. + ValidatorCandidate[] public validatorCandidates; + + /// @dev Mapping from delegator address => consensus address => delegated amount. + mapping(address => mapping(address => uint256)) delegatedAmount; + + // TODO: expose this fn in the interface /// @dev Configuration of maximum number of validator - uint256 validatorThreshold; + uint256 public totalValidatorThreshold; + + // TODO: expose this fn in the interface /// @dev Configuration of number of blocks that validator has to wait before unstaking, counted from staking time - uint256 unstakingOnHoldBlocksNum; - /// @dev Configuration of minimum balance for being a validator - uint256 minValidatorBalance; + uint256 public unstakingOnHoldBlocksNum; - event ProposedValidator(address indexed consensusAddr, address indexed stakingAddr, uint256 amount); - event Staked(address indexed validator, uint256 amount); - event Unstaked(address indexed validator, uint256 amount); - event Delegated(address indexed delegator, address indexed validator, uint256 amount); - event Undelegated(address indexed delegator, address indexed validator, uint256 amount); + // TODO: expose this fn in the interface + /// @dev Configuration of minimum balance for being a validator + uint256 public minValidatorBalance; constructor() { _disableInitializers(); } + /** + * @dev Initializes the contract storage. + */ function initialize() external initializer { - validatorThreshold = 50; + /// TODO: bring this constant variable to params. + totalValidatorThreshold = 50; unstakingOnHoldBlocksNum = 28800; // 28800 blocks ~= 1 day - minValidatorBalance = 1e25; // 10M RON - + minValidatorBalance = 3 * 1e6 * 1e18; // 3m RON /// Add empty validator at 0-index - validators.push(); + validatorCandidates.push(); } - /// - /// VALIDATOR FUNCTIONS - /// - + /** + * @dev See {IStaking-proposeValidator}. + */ function proposeValidator( address _consensusAddr, - address payable _feeAddr, - uint256 _commissionRate, - uint256 _amount - ) external payable returns (uint256 index_) { - require(validators.length < validatorThreshold, "Validators threshold exceeded"); - require(msg.value == _amount, "Transfer RON failed"); - require(!_existValidator(_consensusAddr), "Validator existed"); - - (uint256 index, ValidatorInfo storage _currValidator) = _newValidator(_consensusAddr); - - _currValidator.consensusAddress = _consensusAddr; - _currValidator.stakingAddress = msg.sender; - _currValidator.feeAddress = _feeAddr; - _currValidator.staked = _amount; - _currValidator.balance = _amount; - _currValidator.lastStakingBlock = block.number; - - emit ProposedValidator(_consensusAddr, msg.sender, _amount); - - return index; + address payable _treasuryAddr, + uint256 _commissionRate + ) external payable returns (uint256 _candidateIdx) { + uint256 _amount = msg.value; + address _stakingAddr = msg.sender; + + require(validatorCandidates.length < totalValidatorThreshold, "Staking: query for exceeded validator array length"); + require(_validatorIndexes[_consensusAddr] == 0, "Staking: query for existed candidate"); + require(_amount > minValidatorBalance, "Staking: insuficient amount"); + require(_treasuryAddr.send(0), "Staking: invalid treasury address"); + + _candidateIdx = validatorCandidates.length; + ValidatorCandidate storage _candidate = validatorCandidates.push(); + _candidate.consensusAddr = _consensusAddr; + _candidate.stakingAddr = _stakingAddr; + _candidate.treasuryAddr = _treasuryAddr; + _candidate.commissionRate = _commissionRate; + _candidate.stakedAmount = _amount; + + emit ValidatorProposed(_consensusAddr, _stakingAddr, _amount, _candidate); } - function stake(address _consensusAddress, uint256 _amount) external payable { - require(msg.value == _amount, "Transfer RON failed"); - - ValidatorInfo storage currValidator = _getValidator(_consensusAddress); - currValidator.staked += _amount; - currValidator.stakedDiff += int256(_amount); - currValidator.lastStakingBlock = block.number; + /** + * @dev See {IStaking-stake}. + */ + function stake(address _consensusAddr) external payable { + uint256 _amount = msg.value; + ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); + require(_candidate.stakingAddr == msg.sender, "Staking: invalid staking address"); - emit Staked(_consensusAddress, _amount); + _candidate.stakedAmount += _amount; + emit Staked(_consensusAddr, _amount); } - function unstake(address _consensusAddress, uint256 _amount) external { - ValidatorInfo storage _currValidator = _getValidator(_consensusAddress); - require(msg.sender == _currValidator.stakingAddress, "Caller must be the staker"); - require(block.number >= _currValidator.lastStakingBlock + unstakingOnHoldBlocksNum, "Staking is on hold period"); - - uint256 remainingBalance = _currValidator.staked - _amount; - require(remainingBalance >= minValidatorBalance || remainingBalance == 0, "Invalid unstaking amount"); + /** + * @dev See {IStaking-unstake}. + */ + function unstake(address _consensusAddr, uint256 _amount) external { + ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); + require(_candidate.stakingAddr == msg.sender, "Staking: invalid staking address"); + require(_amount < _candidate.stakedAmount, "Staking: insufficient staked amount"); - _currValidator.stakedDiff -= int256(_amount); - _currValidator.balance = (_currValidator.staked == 0) ? 0 : _currValidator.balance - _amount; + uint256 remainAmount = _candidate.stakedAmount - _amount; + require(remainAmount >= minValidatorBalance, "Staking: invalid staked amount left"); - emit Unstaked(_currValidator.consensusAddress, _amount); + _candidate.stakedAmount = _amount; + emit Unstaked(_consensusAddr, _amount); } - /// - /// DELEGATOR FUNCTIONS - /// - - function delegate(address _validatorAddr, uint256 _amount) external payable { - require(msg.value == _amount, "Transfer RON failed"); - - DelegatorInfo storage _currDelegator = delegators[msg.sender]; - _currDelegator.balance += _amount; - _currDelegator.delegatedOfValidator[_validatorAddr] += _amount; - - ValidatorInfo storage _currValidator = _getValidator(_validatorAddr); - _currValidator.stakedDiff += int256(_amount); - - emit Delegated(msg.sender, _validatorAddr, _amount); + /** + * @dev See {IStaking-delegate}. + */ + function delegate(address _consensusAddr) public payable { + _delegate(msg.sender, _consensusAddr, msg.value); } - function undelegate(address _validatorAddr, uint256 _amount) external { - DelegatorInfo storage _currDelegator = delegators[msg.sender]; - require (_currDelegator.delegatedOfValidator[_validatorAddr] >= _amount, "Invalid undelegating amount"); - - _currDelegator.balance -= _amount; - _currDelegator.delegatedOfValidator[_validatorAddr] -= _amount; + /** + * @dev See {IStaking-delegate}. + */ + function undelegate(address _consensusAddr, uint256 _amount) public { + _undelegate(msg.sender, _consensusAddr, _amount); + require(payable(msg.sender).send(_amount), "Staking: could not transfer RON"); + } - ValidatorInfo storage _currValidator = _getValidator(_validatorAddr); - _currValidator.stakedDiff -= int256(_amount); + /** + * @dev TODO: move to IStaking.sol + */ + function redelegate( + address _consensusAddrSrc, + address _consensusAddrDst, + uint256 _amount + ) external { + _undelegate(msg.sender, _consensusAddrSrc, _amount); + _delegate(msg.sender, _consensusAddrDst, _amount); + } - emit Undelegated(msg.sender, _validatorAddr, _amount); + /** + * @dev TODO + */ + function getRewards(address[] calldata _consensusAddrList) + external + view + returns (uint256[] memory _pending, uint256[] memory _claimable) + { + revert("Unimplemented"); } - /// - /// HELPER ON VALIDATOR MAPPING FUNCTIONS - /// + /** + * @dev Claims rewards. + */ + function claimRewards(address[] calldata _consensusAddrList, uint256 _amounts) external returns (uint256 _amount) { + revert("Unimplemented"); + } - function _existValidator(address _consensusAddr) private view returns (bool) { - uint256 _index = validatorIndexes[_consensusAddr]; - return (_index != 0); + /** + * @dev Claims all pending rewards and delegates them to the consensus address. + */ + function delegateRewards(address[] calldata _consensusAddrList, address _consensusAddrDst) + external + returns (uint256 _amount) + { + revert("Unimplemented"); } - function _getValidator(address _consensusAddr) private view returns (ValidatorInfo storage) { - (bool success, ValidatorInfo storage currValidator) = _tryGetValidator(_consensusAddr); - require(success, "Nonexistent validator"); - return currValidator; + /** + * @dev Delegates from a validator address. + * + * Requirements: + * - The validator is an existed candidate. + * + * Emits the `Delegated` event. + * + * @notice This function does not verify the `msg.value` with the amount. + */ + function _delegate( + address _delegator, + address _consensusAddr, + uint256 _amount + ) internal { + ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); + _candidate.delegatedAmount += _amount; + delegatedAmount[_delegator][_consensusAddr] += _amount; + + emit Delegated(_delegator, _consensusAddr, _amount); } - function _tryGetValidator(address _consensusAddr) private view returns (bool, ValidatorInfo storage) { - uint256 _index = validatorIndexes[_consensusAddr]; - ValidatorInfo storage _currValidator = validators[_index]; + /** + * @dev Undelegates from a validator address. + * + * Requirements: + * - The validator is an existed candidate. + * - The delegated amount is larger than or equal to the undelegated amount. + * + * Emits the `Undelegated` event. + * + * @notice Consider transferring back the amount of RON after calling this function. + */ + function _undelegate( + address _delegator, + address _consensusAddr, + uint256 _amount + ) internal { + require(delegatedAmount[_delegator][_consensusAddr] >= _amount, "Staking: insufficient amount to undelegate"); - if (_index == 0) return (false, _currValidator); + ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); + _candidate.delegatedAmount -= _amount; + delegatedAmount[_delegator][_consensusAddr] -= _amount; - return (true, _currValidator); + emit Undelegated(_delegator, _consensusAddr, _amount); } - function _newValidator(address _consensusAddr) private returns (uint256, ValidatorInfo storage) { - uint256 index = validators.length; - validatorIndexes[_consensusAddr] = index; - return (index, validators.push()); + /** + * @dev Returns the validator candidate in form storage. + * + * Requirements: + * - The candidate is already existed. + * + */ + function _getCandidate(address _consensusAddr) internal view returns (ValidatorCandidate storage _candidate) { + uint256 _idx = _validatorIndexes[_consensusAddr]; + require(_idx > 0, "Staking: query for nonexistent candidate"); + _candidate = validatorCandidates[_idx]; } - } diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index a7d2754ee..fe279f42e 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -3,62 +3,67 @@ pragma solidity ^0.8.9; interface IStaking { - struct ValidatorInfo { + struct ValidatorCandidate { + /// @dev Address that stakes for the validator, each consensus address has only one staking address. + address stakingAddr; /// @dev Address of the validator that produces block, e.g. block.coinbase. This is so-called validator address. - address consensusAddress; - /// @dev Address that stakes for the validator, each consensus address has only one staking address - address stakingAddress; + address consensusAddr; /// @dev Address that receives mining fee of the validator - address payable feeAddress; - /// @dev Total reward amount - uint256 totalReward; + address payable treasuryAddr; /// @dev The percentile of reward that validators can be received, the rest goes to the delegators uint256 commissionRate; - /// @dev The amount of staked coin in the previous epoch - uint256 staked; - /// @dev The difference between the amount of staked coin in the previous epoch and the current epoch - int256 stakedDiff; - /// @dev Sum of staked amount from validator and delegators - uint256 balance; - /// @dev Last staking block number, used for calculating time condition of unstaking - uint256 lastStakingBlock; + /// @dev The RON amount from the validator. + uint256 stakedAmount; + /// @dev The RON amount from the delegator. + uint256 delegatedAmount; /// @dev For upgrability purpose uint256[20] ____gap; } - struct DelegatorInfo { - /// @dev Total amount of delegated token - uint256 balance; - uint256 totalReward; - uint256 pendingReward; - /// @dev Delegating amount of delegator for each validator (validator consensus address => amount) - mapping(address => uint256) delegatedOfValidator; - /// @dev For upgrability purpose - uint256[20] ____gap; - } + /// @dev TODO: add comment for these events + event ValidatorProposed( + address indexed consensusAddr, + address indexed candidateIdx, + uint256 amount, + ValidatorCandidate _info + ); + event Staked(address indexed validator, uint256 amount); + event Unstaked(address indexed validator, uint256 amount); + event Delegated(address indexed delegator, address indexed validator, uint256 amount); + event Undelegated(address indexed delegator, address indexed validator, uint256 amount); + + // TODO: write comment for this fn. + // TODO: write setter for this fn. + function minValidatorBalance() external view returns (uint256); /** - * @notice First proposing a validator with information, require to stake at least `MINIMUM_STAKING` amount + * @dev Proposes a validator with detailed information. + * + * Requirements: + * - The `msg.value` is at least `MINIMUM_STAKING` amount. + * - TODO: update the requirements. + * + * Emits the `ValidatorProposed` event. * * @return index The index of new validator in the validator list + * */ function proposeValidator( - address consensusAddress, - address payable feeAddress, - uint256 commissionRate, - uint256 amount + address _consensusAddr, + address payable _treasuryAddr, + uint256 _commissionRate ) external payable returns (uint256 index); /** * @notice Stake as validator. */ - function stake(address consensusAddress, uint256 amount) external payable; + function stake(uint256 amount) external payable; /** * @notice Unstake as validator. Need to wait `UNSTAKING_ON_HOLD_BLOCKS_NUM` blocks. * @dev The remain balance must either be greater than `MINIMUM_STAKING` value or equal to zero. */ - function unstake(address consensusAddress, uint256 amount) external; + function unstake(address consensusAddr, uint256 amount) external; /** * @notice Stake as delegator for a validator diff --git a/contracts/interfaces/IValidatorSet.sol b/contracts/interfaces/IValidatorSet.sol index f933be5f8..e6989e72f 100644 --- a/contracts/interfaces/IValidatorSet.sol +++ b/contracts/interfaces/IValidatorSet.sol @@ -11,7 +11,7 @@ interface IValidatorSet { /// @dev Address of the validator that produces block, e.g. block.coinbase address consensusAddress; /// @dev Address that receives mining fee of the validator - address payable feeAddress; + address payable treasuryAddr; /// @dev Total reward amount uint256 totalAmount; /// @dev Remaining time being in jail (not able to produce block) From eec0f23a5d74cfcd53fe2266262c55106abbae2e Mon Sep 17 00:00:00 2001 From: nxqbao Date: Wed, 24 Aug 2022 17:57:41 +0700 Subject: [PATCH 010/190] Update IStaking --- contracts/interfaces/IStaking.sol | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index fe279f42e..588c9ac6f 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -86,12 +86,23 @@ interface IStaking { /** * @notice Update set of validators * - * @dev Add stake and stake_diff for each validator. Obtain currentValidatorSet based on validators + * @dev Sorting the validators by their current balance, then pick the top N validators to be + * assigned to the new set. The result is returned to the `ValidatorSet` contract. * * Requirements: * - Only validator and `ValidatorSet` contract can call this function + * + * @return newValidatorSet Validator set for the new epoch */ - function updateValidatorSet() external; + function updateValidatorSet() external returns (ValidatorCandidate[] memory newValidatorSet); + + /** + * @dev Handle deposit request. Update validators' reward balance and delegators' balance. + * + * Requirements: + * - Only `ValidatorSet` contract cann call this function + */ + function onDeposit() external payable; /** * @notice Distribute reward From 3b6d99c4c74106c8671809f4da0325c28c884ed9 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Wed, 24 Aug 2022 17:58:02 +0700 Subject: [PATCH 011/190] Update ValidatorSet interface --- contracts/interfaces/IValidatorSet.sol | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/contracts/interfaces/IValidatorSet.sol b/contracts/interfaces/IValidatorSet.sol index e6989e72f..5199a3348 100644 --- a/contracts/interfaces/IValidatorSet.sol +++ b/contracts/interfaces/IValidatorSet.sol @@ -9,15 +9,11 @@ pragma solidity ^0.8.9; interface IValidatorSet { struct Validator { /// @dev Address of the validator that produces block, e.g. block.coinbase - address consensusAddress; - /// @dev Address that receives mining fee of the validator - address payable treasuryAddr; - /// @dev Total reward amount - uint256 totalAmount; - /// @dev Remaining time being in jail (not able to produce block) - uint256 jailTime; - /// @dev - uint256 reward; + address consensusAddr; + /// @dev Address of treasury of the validator, used for double-checking validator information + address treasuryAddr; + /// @dev State if the validator is in jail + bool jailed; /// @dev For upgrability purpose /// @custom:note Consider leave or keep this attribute uint256[20] ___gap; From da3ca0e11fee531d47a88dec106d524db060c272 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Wed, 24 Aug 2022 17:58:23 +0700 Subject: [PATCH 012/190] Init commit ValidatorSet implementation --- contracts/ValidatorSet.sol | 165 ++++++++++++++++++++++++++++++ contracts/ValidatorSetStorage.sol | 94 +++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100644 contracts/ValidatorSet.sol create mode 100644 contracts/ValidatorSetStorage.sol diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol new file mode 100644 index 000000000..bddc61a53 --- /dev/null +++ b/contracts/ValidatorSet.sol @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import "./interfaces/IValidatorSet.sol"; +import "./interfaces/IStaking.sol"; +import "./ValidatorSetStorage.sol"; + +/** + * @title Set of validators in current epoch + * @notice This contract maintains set of validator in the current epoch of Ronin network + */ +contract ValidatorSet is ValidatorSetStorage { + using EnumerableMap for EnumerableMap.AddressToUintMap; + + uint256 public constant INIT_NUM_OF_CABINETS = 21; + uint256 public constant INIT_EPOCH_LENGTH = 200; + + uint256 numOfCabinets; + uint256 epochLength; + uint256 lastUpdated; + + IStaking stakingContract; + + event ValidatorSetUpdated(); + event ValidatorJailed(address indexed validator); + /// @dev Event for each time the validators deposit mining reward + event ValidatorDeposited(address indexed validator, uint256 amount); + /// @dev Event for the in-jail validators deposit mining reward + event DeprecatedDeposit(address indexed validator, uint256 amount); + + modifier onlyCoinbase() { + require(tx.origin == block.coinbase, "Only coinbase"); + _; + } + + modifier onlyValidator() { + require(isValidator(msg.sender), "Only validator"); + _; + } + + modifier noEmptyDeposit() { + require(msg.value > 0, "No empty deposit"); + _; + } + + modifier atEpochEnding() { + require((block.number + 1) % epochLength == 0, "Only at the end of the epoch"); + _; + } + + constructor(IStaking _stakingContract) { + stakingContract = _stakingContract; + numOfCabinets = INIT_NUM_OF_CABINETS; + epochLength = INIT_EPOCH_LENGTH; + } + + function getValidators() external view returns (address[] memory) { + uint256 size = currentValidatorIndexesMap.length(); + address[] memory validatorAddresses = new address[](size); + for (uint256 i = 0; i < size; i++) { + Validator memory _v = _getValidatorAtMiningIndex(i); + validatorAddresses[i] = _v.consensusAddr; + } + return validatorAddresses; + } + + function updateValidators() external onlyValidator atEpochEnding returns (address[] memory) { + // 1. fetch new validator set from staking contract + IStaking.ValidatorCandidate[] memory upcommingValidatorSet = stakingContract.updateValidatorSet(); + + // 2. update last updated + lastUpdated = block.number; + + // 3. update new validator list + // TODO: + } + + function getLastUpdated() external view returns (uint256 height) { + return lastUpdated; + } + + function depositReward() external payable onlyCoinbase { + uint256 _value = msg.value; + address _valAddr = tx.origin; + Validator storage _validator = _getValidator(_valAddr); + + // 1. check if validator is in current epoch + if (currentValidatorIndexesMap.contains(_valAddr)) { + // 2. check if validator is in jail + if (_validator.jailed) { + emit DeprecatedDeposit(_valAddr, _value); + } else { + stakingContract.onDeposit(_valAddr, _value); + } + } + + // 3. if get deposit from deprecated validators + emit DeprecatedDeposit(_valAddr, _value); + } + + function slashMisdemeanor(address validator) external { + revert("Unimplemented"); + } + + /// TODO: + function slashFelony(address validator) external { + revert("Unimplemented"); + } + + /// TODO: + function slashDoubleSign(address validator) external { + revert("Unimplemented"); + } + + /// + /// INTERNAL FUNCTIONS + /// + + function doUpdateState(IStaking.ValidatorCandidate[] memory _incomingValidatorSet) private { + uint256 n = currentValidatorIndexesMap.length(); + uint256 m = _incomingValidatorSet.length; + uint256 k = n < m ? n : m; + + // // keep the validators that already in the exact order + // for (uint256 i = 0; i < k; ++i) { + // Validator memory _oldValidator = validatorSet[currentValidatorIndexesMap.at(i)]; + // if (_oldValidator.consensusAddr != _incomingValidatorSet[i].consensusAddr) { + // currentValidatorIndexesMap.set(_oldValidator.consensusAddr, 0); + // } + // } + + // // in case the current set is longer, delete trailing members in the current set + // if (n > m) { + // for (uint256 i = m; i < n; ++i) { + // Validator memory _oldValidator = validatorSet[currentValidatorIndexesMap.at(i)]; + // currentValidatorIndexesMap.set(_oldValidator.consensusAddr, 0); + // } + // } + + // // update the set by traversing each index + // for (uint256 i = 0; i < k; ++i) { + // if (!_isSameValidator(_incomingValidatorSet[i], currentValidatorSet[i])) { + // currentValidatorSetMap[_incomingValidatorSet[i].consensusAddr] = i; + // _updateValidatorIndex(_incomingValidatorSet[i], currentValidatorSet[i]); + // } + // } + + // // update the set by appending to the current set + // if (m > n) { + // for (uint256 i = n; i < m; ++i) { + // _addOneValidator(_incomingValidatorSet[i]); + // } + // } + } + + function isValidator(address _addr) public view returns (bool) { + return validatorSetMap[_addr] > 0; + } + + function isCurrentValidator(address _addr) public view returns (bool) { + return currentValidatorIndexesMap.contains(_addr); + } +} diff --git a/contracts/ValidatorSetStorage.sol b/contracts/ValidatorSetStorage.sol new file mode 100644 index 000000000..b6cc4f0e5 --- /dev/null +++ b/contracts/ValidatorSetStorage.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import "./interfaces/IValidatorSet.sol"; +import "./interfaces/IStaking.sol"; + +/** + * @title Set of validators in current epoch + * @notice This contract maintains set of validator in the current epoch of Ronin network + */ +abstract contract ValidatorSetStorage is IValidatorSet { + using EnumerableMap for EnumerableMap.AddressToUintMap; + + /// @dev Array of all validators. The element at 0-slot is reserved for unknown validator. + Validator[] public validatorSet; + /// @dev Map of array of all validator + mapping(address => uint256) public validatorSetMap; + /// @dev Enumerable map of indexes of the current validator set, e.g. the array [3, 4, 1] tells + /// the sequence of validators to mine block in the next epoch will be at the 3-, 4- and 1-index, + /// One can query the mining order of an arbitrary address by this enumerable map. + EnumerableMap.AddressToUintMap internal currentValidatorIndexesMap; + + function _setValidatorAtMiningIndex(uint256 _miningIndex, IStaking.ValidatorCandidate memory _incomingValidator) + internal + { + address _newValAddr = _incomingValidator.consensusAddr; + uint256 _index = _setValidator(_newValAddr, _incomingValidator); + + (address _oldValAddr,) = currentValidatorIndexesMap.at(_miningIndex); + if (_oldValAddr != _newValAddr) { + currentValidatorIndexesMap.remove(_oldValAddr); + currentValidatorIndexesMap.set(_newValAddr, _index); + } + } + + function _setValidator(address _valAddr, IStaking.ValidatorCandidate memory _incomingValidator) + internal + returns (uint256) + { + (bool _success, Validator storage _currentValidator) = _tryGetValidator(_valAddr); + if (_success) { + return __setExistedValidator(_incomingValidator, _currentValidator); + } + return __setUnexistentValidator(_incomingValidator); + } + + function __setExistedValidator( + IStaking.ValidatorCandidate memory _incomingValidator, + Validator storage _currentValidator + ) internal returns (uint256) { + _currentValidator.consensusAddr = _incomingValidator.consensusAddr; + _currentValidator.treasuryAddr = _incomingValidator.treasuryAddr; + + return validatorSetMap[_currentValidator.consensusAddr]; + } + + function __setUnexistentValidator(IStaking.ValidatorCandidate memory _incomingValidator) internal returns (uint256) { + uint256 index = validatorSet.length; + + validatorSetMap[_incomingValidator.consensusAddr] = index; + Validator storage _v = validatorSet.push(); + _v.consensusAddr = _incomingValidator.consensusAddr; + _v.treasuryAddr = _incomingValidator.treasuryAddr; + + return index; + } + + function _getValidatorAtMiningIndex(uint256 _miningIndex) internal view returns (Validator storage) { + (, uint256 _index) = currentValidatorIndexesMap.at(_miningIndex); + require(_index != 0, string(abi.encodePacked("Nonexistent validator at index ", _miningIndex))); + return validatorSet[_index]; + } + + function _getValidator(address _valAddr) internal view returns (Validator storage) { + (bool _success, Validator storage _v) = _tryGetValidator(_valAddr); + require(_success, string(abi.encodePacked("Nonexistent validator ", _valAddr))); + return _v; + } + + function _tryGetValidator(address _valAddr) internal view returns (bool, Validator storage) { + uint256 _index = validatorSetMap[_valAddr]; + Validator storage _v = validatorSet[_index]; + if (_index == 0) { + return (false, _v); + } + return (true, _v); + } + + function _isSameValidator(IStaking.ValidatorCandidate memory _v1, Validator memory _v2) internal pure returns (bool) { + return _v1.consensusAddr == _v2.consensusAddr && _v1.treasuryAddr == _v2.treasuryAddr; + } +} From 55d1e0fb59c73a88d856615312d6d3d2a5f93bf0 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Thu, 25 Aug 2022 16:05:30 +0700 Subject: [PATCH 013/190] Update ValidatorSet contract --- contracts/ValidatorSet.sol | 105 +++++++++++++++--------------- contracts/ValidatorSetStorage.sol | 45 +++++++------ 2 files changed, 79 insertions(+), 71 deletions(-) diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index bddc61a53..86582e0c0 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -56,25 +56,18 @@ contract ValidatorSet is ValidatorSetStorage { epochLength = INIT_EPOCH_LENGTH; } - function getValidators() external view returns (address[] memory) { - uint256 size = currentValidatorIndexesMap.length(); - address[] memory validatorAddresses = new address[](size); - for (uint256 i = 0; i < size; i++) { - Validator memory _v = _getValidatorAtMiningIndex(i); - validatorAddresses[i] = _v.consensusAddr; - } - return validatorAddresses; - } - function updateValidators() external onlyValidator atEpochEnding returns (address[] memory) { // 1. fetch new validator set from staking contract - IStaking.ValidatorCandidate[] memory upcommingValidatorSet = stakingContract.updateValidatorSet(); + IStaking.ValidatorCandidate[] memory _upcommingValidatorSet = stakingContract.updateValidatorSet(); // 2. update last updated lastUpdated = block.number; - // 3. update new validator list - // TODO: + // 3. update new validator set + _doUpdateState(_upcommingValidatorSet); + + // 4. return new validator set + return getValidators(); } function getLastUpdated() external view returns (uint256 height) { @@ -92,7 +85,7 @@ contract ValidatorSet is ValidatorSetStorage { if (_validator.jailed) { emit DeprecatedDeposit(_valAddr, _value); } else { - stakingContract.onDeposit(_valAddr, _value); + stakingContract.onDeposit(); } } @@ -114,52 +107,62 @@ contract ValidatorSet is ValidatorSetStorage { revert("Unimplemented"); } + function getValidators() public view returns (address[] memory) { + uint256 size = currentValidatorIndexesMap.length(); + address[] memory validatorAddresses = new address[](size); + for (uint256 i = 0; i < size; i++) { + Validator memory _v = _getValidatorAtMiningIndex(i); + validatorAddresses[i] = _v.consensusAddr; + } + return validatorAddresses; + } + + function isValidator(address _addr) public view returns (bool) { + return validatorSetMap[_addr] > 0; + } + + function isCurrentValidator(address _addr) public view returns (bool) { + return currentValidatorIndexesMap.contains(_addr); + } + /// /// INTERNAL FUNCTIONS /// - function doUpdateState(IStaking.ValidatorCandidate[] memory _incomingValidatorSet) private { + function _doUpdateState(IStaking.ValidatorCandidate[] memory _incomingValidatorSet) private { uint256 n = currentValidatorIndexesMap.length(); uint256 m = _incomingValidatorSet.length; uint256 k = n < m ? n : m; - // // keep the validators that already in the exact order - // for (uint256 i = 0; i < k; ++i) { - // Validator memory _oldValidator = validatorSet[currentValidatorIndexesMap.at(i)]; - // if (_oldValidator.consensusAddr != _incomingValidatorSet[i].consensusAddr) { - // currentValidatorIndexesMap.set(_oldValidator.consensusAddr, 0); - // } - // } - - // // in case the current set is longer, delete trailing members in the current set - // if (n > m) { - // for (uint256 i = m; i < n; ++i) { - // Validator memory _oldValidator = validatorSet[currentValidatorIndexesMap.at(i)]; - // currentValidatorIndexesMap.set(_oldValidator.consensusAddr, 0); - // } - // } - - // // update the set by traversing each index - // for (uint256 i = 0; i < k; ++i) { - // if (!_isSameValidator(_incomingValidatorSet[i], currentValidatorSet[i])) { - // currentValidatorSetMap[_incomingValidatorSet[i].consensusAddr] = i; - // _updateValidatorIndex(_incomingValidatorSet[i], currentValidatorSet[i]); - // } - // } - - // // update the set by appending to the current set - // if (m > n) { - // for (uint256 i = n; i < m; ++i) { - // _addOneValidator(_incomingValidatorSet[i]); - // } - // } - } + // keep the validators that already in the exact order and update wrong validators + // incoming set: [ ========= ] + // current set: [ ===== ] + // updating part: ^^^^^ + for (uint256 i = 0; i < k; ++i) { + Validator memory _oldValidator = _getValidatorAtMiningIndex(i); + if (!_isSameValidator(_incomingValidatorSet[i], _oldValidator)) { + _setValidatorAtMiningIndex(i, _incomingValidatorSet[i]); + } + } - function isValidator(address _addr) public view returns (bool) { - return validatorSetMap[_addr] > 0; - } + // in case the incoming set is longer, update the set by appending to the current set + // incoming set: [ ========= ] + // current set: [ ===== ] + // updating part: ^^^^ + if (m > n) { + for (uint256 i = n; i < m; ++i) { + _setValidatorAtMiningIndex(i, _incomingValidatorSet[i]); + } + } - function isCurrentValidator(address _addr) public view returns (bool) { - return currentValidatorIndexesMap.contains(_addr); + // in case the current set is longer, delete trailing members in the current set + // incoming set: [ === ] + // current set: [ ========= ] + // updating part: ^^^^^^ + if (n > m) { + for (uint256 i = m; i < n; ++i) { + _removeValidatorAtMiningIndex(i); + } + } } } diff --git a/contracts/ValidatorSetStorage.sol b/contracts/ValidatorSetStorage.sol index b6cc4f0e5..72d6ccfd1 100644 --- a/contracts/ValidatorSetStorage.sol +++ b/contracts/ValidatorSetStorage.sol @@ -28,7 +28,7 @@ abstract contract ValidatorSetStorage is IValidatorSet { address _newValAddr = _incomingValidator.consensusAddr; uint256 _index = _setValidator(_newValAddr, _incomingValidator); - (address _oldValAddr,) = currentValidatorIndexesMap.at(_miningIndex); + (address _oldValAddr, ) = currentValidatorIndexesMap.at(_miningIndex); if (_oldValAddr != _newValAddr) { currentValidatorIndexesMap.remove(_oldValAddr); currentValidatorIndexesMap.set(_newValAddr, _index); @@ -46,25 +46,9 @@ abstract contract ValidatorSetStorage is IValidatorSet { return __setUnexistentValidator(_incomingValidator); } - function __setExistedValidator( - IStaking.ValidatorCandidate memory _incomingValidator, - Validator storage _currentValidator - ) internal returns (uint256) { - _currentValidator.consensusAddr = _incomingValidator.consensusAddr; - _currentValidator.treasuryAddr = _incomingValidator.treasuryAddr; - - return validatorSetMap[_currentValidator.consensusAddr]; - } - - function __setUnexistentValidator(IStaking.ValidatorCandidate memory _incomingValidator) internal returns (uint256) { - uint256 index = validatorSet.length; - - validatorSetMap[_incomingValidator.consensusAddr] = index; - Validator storage _v = validatorSet.push(); - _v.consensusAddr = _incomingValidator.consensusAddr; - _v.treasuryAddr = _incomingValidator.treasuryAddr; - - return index; + function _removeValidatorAtMiningIndex(uint256 _miningIndex) internal { + (address _oldValAddr, ) = currentValidatorIndexesMap.at(_miningIndex); + currentValidatorIndexesMap.remove(_oldValAddr); } function _getValidatorAtMiningIndex(uint256 _miningIndex) internal view returns (Validator storage) { @@ -91,4 +75,25 @@ abstract contract ValidatorSetStorage is IValidatorSet { function _isSameValidator(IStaking.ValidatorCandidate memory _v1, Validator memory _v2) internal pure returns (bool) { return _v1.consensusAddr == _v2.consensusAddr && _v1.treasuryAddr == _v2.treasuryAddr; } + + function __setExistedValidator( + IStaking.ValidatorCandidate memory _incomingValidator, + Validator storage _currentValidator + ) private returns (uint256) { + _currentValidator.consensusAddr = _incomingValidator.consensusAddr; + _currentValidator.treasuryAddr = _incomingValidator.treasuryAddr; + + return validatorSetMap[_currentValidator.consensusAddr]; + } + + function __setUnexistentValidator(IStaking.ValidatorCandidate memory _incomingValidator) private returns (uint256) { + uint256 index = validatorSet.length; + + validatorSetMap[_incomingValidator.consensusAddr] = index; + Validator storage _v = validatorSet.push(); + _v.consensusAddr = _incomingValidator.consensusAddr; + _v.treasuryAddr = _incomingValidator.treasuryAddr; + + return index; + } } From a68f4162a46c9abcabc3f609c192c8a66ad40932 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Thu, 25 Aug 2022 16:15:04 +0700 Subject: [PATCH 014/190] [Validator] Add isWorkingValidator function --- contracts/ValidatorSet.sol | 5 +++++ contracts/interfaces/IValidatorSet.sol | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index 86582e0c0..19bcf7a39 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -121,6 +121,11 @@ contract ValidatorSet is ValidatorSetStorage { return validatorSetMap[_addr] > 0; } + function isWorkingValidator(address _addr) public view returns (bool) { + Validator memory _v = _getValidator(_addr); + return (!_v.jailed); + } + function isCurrentValidator(address _addr) public view returns (bool) { return currentValidatorIndexesMap.contains(_addr); } diff --git a/contracts/interfaces/IValidatorSet.sol b/contracts/interfaces/IValidatorSet.sol index 5199a3348..957f6c086 100644 --- a/contracts/interfaces/IValidatorSet.sol +++ b/contracts/interfaces/IValidatorSet.sol @@ -73,6 +73,16 @@ interface IValidatorSet { */ function getValidators() external view returns (address[] memory); + /** + * @notice Check if an address is a validator + */ + function isValidator(address validator) external view returns (bool); + + /** + * @notice Check if an address is a validator not being in jail + */ + function isWorkingValidator(address validator) external view returns (bool); + /** * @notice Check if an address is in the validator list of the current epoch */ From 7929908c5f7404502191da1e1c99744b6634afe2 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Thu, 25 Aug 2022 16:16:34 +0700 Subject: [PATCH 015/190] [Validator] Fix updateValidators function --- contracts/ValidatorSet.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index 19bcf7a39..11257c448 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -65,7 +65,8 @@ contract ValidatorSet is ValidatorSetStorage { // 3. update new validator set _doUpdateState(_upcommingValidatorSet); - + emit ValidatorSetUpdated(); + // 4. return new validator set return getValidators(); } From 84c07d452b32fabf85f86a97d4868fb4402a0832 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Thu, 25 Aug 2022 16:36:42 +0700 Subject: [PATCH 016/190] [Validator] Update _setValidator function --- contracts/ValidatorSetStorage.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/ValidatorSetStorage.sol b/contracts/ValidatorSetStorage.sol index 72d6ccfd1..31f96b584 100644 --- a/contracts/ValidatorSetStorage.sol +++ b/contracts/ValidatorSetStorage.sol @@ -26,7 +26,7 @@ abstract contract ValidatorSetStorage is IValidatorSet { internal { address _newValAddr = _incomingValidator.consensusAddr; - uint256 _index = _setValidator(_newValAddr, _incomingValidator); + uint256 _index = _setValidator(_incomingValidator, false); (address _oldValAddr, ) = currentValidatorIndexesMap.at(_miningIndex); if (_oldValAddr != _newValAddr) { @@ -35,12 +35,12 @@ abstract contract ValidatorSetStorage is IValidatorSet { } } - function _setValidator(address _valAddr, IStaking.ValidatorCandidate memory _incomingValidator) + function _setValidator(IStaking.ValidatorCandidate memory _incomingValidator, bool _forced) internal returns (uint256) { - (bool _success, Validator storage _currentValidator) = _tryGetValidator(_valAddr); - if (_success) { + (bool _success, Validator storage _currentValidator) = _tryGetValidator(_incomingValidator.consensusAddr); + if (_success && _forced) { return __setExistedValidator(_incomingValidator, _currentValidator); } return __setUnexistentValidator(_incomingValidator); From 88c0c64bd1c854850cf3504f704e1056c371cb7b Mon Sep 17 00:00:00 2001 From: nxqbao Date: Thu, 25 Aug 2022 17:28:50 +0700 Subject: [PATCH 017/190] [Validator] Update validator array --- contracts/ValidatorSetStorage.sol | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contracts/ValidatorSetStorage.sol b/contracts/ValidatorSetStorage.sol index 31f96b584..149007fe3 100644 --- a/contracts/ValidatorSetStorage.sol +++ b/contracts/ValidatorSetStorage.sol @@ -19,9 +19,15 @@ abstract contract ValidatorSetStorage is IValidatorSet { mapping(address => uint256) public validatorSetMap; /// @dev Enumerable map of indexes of the current validator set, e.g. the array [3, 4, 1] tells /// the sequence of validators to mine block in the next epoch will be at the 3-, 4- and 1-index, - /// One can query the mining order of an arbitrary address by this enumerable map. + /// One can query the mining order of an arbitrary address by this enumerable map. This array is + /// indexed from 0, but its content is non-zero value, due to reserved 0-slot in `validatorSet`. EnumerableMap.AddressToUintMap internal currentValidatorIndexesMap; + constructor () { + // Add empty validator at 0-slot for the set map + validatorSet.push(); + } + function _setValidatorAtMiningIndex(uint256 _miningIndex, IStaking.ValidatorCandidate memory _incomingValidator) internal { From c8f392bd14d8a82c4015b5a3a844f002b6991ac7 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Thu, 25 Aug 2022 17:29:47 +0700 Subject: [PATCH 018/190] [Validator] Add more view functions --- contracts/ValidatorSet.sol | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index 11257c448..8dbc634cc 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -14,12 +14,12 @@ import "./ValidatorSetStorage.sol"; contract ValidatorSet is ValidatorSetStorage { using EnumerableMap for EnumerableMap.AddressToUintMap; - uint256 public constant INIT_NUM_OF_CABINETS = 21; - uint256 public constant INIT_EPOCH_LENGTH = 200; + uint256 private constant INIT_NUM_OF_CABINETS = 21; + uint256 private constant INIT_EPOCH_LENGTH = 200; - uint256 numOfCabinets; - uint256 epochLength; - uint256 lastUpdated; + uint256 public numOfCabinets; + uint256 public epochLength; + uint256 public lastUpdated; IStaking stakingContract; @@ -108,6 +108,14 @@ contract ValidatorSet is ValidatorSetStorage { revert("Unimplemented"); } + function updateNumOfCabinets(uint256 _numOfCabinets) external onlyCoinbase { + revert("Unimplemented"); + } + + function updateEpochLength(uint256 _epochLength) external onlyCoinbase { + revert("Unimplemented"); + } + function getValidators() public view returns (address[] memory) { uint256 size = currentValidatorIndexesMap.length(); address[] memory validatorAddresses = new address[](size); From 40ba7148bb29f836e57f07adf0e410a0596745a2 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Thu, 25 Aug 2022 17:30:08 +0700 Subject: [PATCH 019/190] [Validator] Fix updateValidators function --- contracts/ValidatorSet.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index 8dbc634cc..6b0bb07ba 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -59,6 +59,7 @@ contract ValidatorSet is ValidatorSetStorage { function updateValidators() external onlyValidator atEpochEnding returns (address[] memory) { // 1. fetch new validator set from staking contract IStaking.ValidatorCandidate[] memory _upcommingValidatorSet = stakingContract.updateValidatorSet(); + require(_upcommingValidatorSet.length <= numOfCabinets, "Exceeds maximum validators per epoch"); // 2. update last updated lastUpdated = block.number; From e6d9c5334bbb8f92e40a206faf6ffcc4f3e81783 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Thu, 25 Aug 2022 17:39:06 +0700 Subject: [PATCH 020/190] [Validator] Rename contract name --- contracts/ValidatorSet.sol | 4 ++-- contracts/{ValidatorSetStorage.sol => ValidatorSetBase.sol} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename contracts/{ValidatorSetStorage.sol => ValidatorSetBase.sol} (98%) diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index 6b0bb07ba..a9cecdafa 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -5,13 +5,13 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "./interfaces/IValidatorSet.sol"; import "./interfaces/IStaking.sol"; -import "./ValidatorSetStorage.sol"; +import "./ValidatorSetBase.sol"; /** * @title Set of validators in current epoch * @notice This contract maintains set of validator in the current epoch of Ronin network */ -contract ValidatorSet is ValidatorSetStorage { +contract ValidatorSet is ValidatorSetBase { using EnumerableMap for EnumerableMap.AddressToUintMap; uint256 private constant INIT_NUM_OF_CABINETS = 21; diff --git a/contracts/ValidatorSetStorage.sol b/contracts/ValidatorSetBase.sol similarity index 98% rename from contracts/ValidatorSetStorage.sol rename to contracts/ValidatorSetBase.sol index 149007fe3..8c7a29967 100644 --- a/contracts/ValidatorSetStorage.sol +++ b/contracts/ValidatorSetBase.sol @@ -10,7 +10,7 @@ import "./interfaces/IStaking.sol"; * @title Set of validators in current epoch * @notice This contract maintains set of validator in the current epoch of Ronin network */ -abstract contract ValidatorSetStorage is IValidatorSet { +abstract contract ValidatorSetBase is IValidatorSet { using EnumerableMap for EnumerableMap.AddressToUintMap; /// @dev Array of all validators. The element at 0-slot is reserved for unknown validator. From 8483d9a1a0caafb22f7eeea936f4d830591efef8 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 30 Aug 2022 10:38:50 +0700 Subject: [PATCH 021/190] [Staking] Add sorting validators --- .prettierrc.json | 2 +- contracts/Staking.sol | 58 +++++++++++++++++++++++++ contracts/interfaces/IStaking.sol | 17 +++++++- contracts/libraries/QuickSort.sol | 33 -------------- contracts/libraries/Sorting.sol | 72 +++++++++++++++++++++++++++++++ 5 files changed, 146 insertions(+), 36 deletions(-) delete mode 100644 contracts/libraries/QuickSort.sol create mode 100644 contracts/libraries/Sorting.sol diff --git a/.prettierrc.json b/.prettierrc.json index 7d7e4fa5b..f6088f9ee 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -3,7 +3,7 @@ "tabWidth": 2, "useTabs": false, "bracketSpacing": true, - "explicitTypes": "always", + "explicitTypes": "preserve", "overrides": [ { "files": "*.sol", diff --git a/contracts/Staking.sol b/contracts/Staking.sol index 48c561c42..592e44711 100644 --- a/contracts/Staking.sol +++ b/contracts/Staking.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "./interfaces/IStaking.sol"; +import "./interfaces/IValidatorSet.sol"; +import "./libraries/Sorting.sol"; abstract contract Staking is IStaking, Initializable { /// TODO: expose fn to get validator info by validator address. @@ -28,6 +30,17 @@ abstract contract Staking is IStaking, Initializable { /// @dev Configuration of minimum balance for being a validator uint256 public minValidatorBalance; + uint256[] internal currentValidatorIndexes; + + IValidatorSet validatorSetContract; + + uint256 public numOfCabinets; + + modifier onlyValidatorSetContract() { + require(msg.sender == address(validatorSetContract), "Only validator set contract"); + _; + } + constructor() { _disableInitializers(); } @@ -212,4 +225,49 @@ abstract contract Staking is IStaking, Initializable { require(_idx > 0, "Staking: query for nonexistent candidate"); _candidate = validatorCandidates[_idx]; } + + /** + * @notice Update set of validators + * + * @dev Sorting the validators by their current balance, then pick the top N validators to be + * assigned to the new set. The result is returned to the `ValidatorSet` contract. + * + * Requirements: + * - Only validator and `ValidatorSet` contract can call this function + * + * @return newValidatorSet Validator set for the new epoch + */ + function updateValidatorSet() external returns (ValidatorCandidate[] memory newValidatorSet) { + uint _length = validatorCandidates.length; + Sorting.Node[] memory _nodes = new Sorting.Node[](_length); + Sorting.Node[] memory _sortedNodes = new Sorting.Node[](_length); + + for (uint i = 0; i < _length; i++) { + ValidatorCandidate memory _validator = validatorCandidates[i]; + + _nodes[i].key = i; + _nodes[i].value = _validator.stakedAmount + _validator.delegatedAmount; + } + + delete currentValidatorIndexes; + _sortedNodes = Sorting.sortNodes(_nodes); + + /// TODO: pick M validators which are governance + uint _currentSetSize = _length < numOfCabinets ? _length : numOfCabinets; + for (uint i = 0; i < _currentSetSize; i++) { + currentValidatorIndexes.push(_sortedNodes[i].value); + } + + return getCurrentValidatorSet(); + } + + function getCurrentValidatorSet() public view returns (ValidatorCandidate[] memory currentValidatorSet_) { + uint _length = currentValidatorIndexes.length; + currentValidatorSet_ = new ValidatorCandidate[](currentValidatorIndexes.length); + for (uint i = 0; i < _length; i++) { + currentValidatorSet_[i] = validatorCandidates[currentValidatorIndexes[i]]; + } + + return currentValidatorSet_; + } } diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 588c9ac6f..1f8e024e7 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -16,6 +16,8 @@ interface IStaking { uint256 stakedAmount; /// @dev The RON amount from the delegator. uint256 delegatedAmount; + /// @dev Mark the validator is a governance node + bool governing; /// @dev For upgrability purpose uint256[20] ____gap; } @@ -87,8 +89,12 @@ interface IStaking { * @notice Update set of validators * * @dev Sorting the validators by their current balance, then pick the top N validators to be - * assigned to the new set. The result is returned to the `ValidatorSet` contract. - * + * assigned to the new set. The result is returned to the `ValidatorSet` contract. There will be + * a threshold of M validator must be come from the governance. In case this threshold is set, + * M slots are reversed for the governing validator. The total of validators is configured in the + * `numOfCabinets` variable. If the actual number of validator candidates is not met, all of the + * candidates will become the validators. + * * Requirements: * - Only validator and `ValidatorSet` contract can call this function * @@ -96,6 +102,13 @@ interface IStaking { */ function updateValidatorSet() external returns (ValidatorCandidate[] memory newValidatorSet); + /** + * @notice Get current validator set. Number of current validator is set in `numOfCabinets`. + * + * @dev If the actual number of candidates is lower, all of the candidates are returned + */ + function getCurrentValidatorSet() external returns (ValidatorCandidate[] memory currentValidatorSet); + /** * @dev Handle deposit request. Update validators' reward balance and delegators' balance. * diff --git a/contracts/libraries/QuickSort.sol b/contracts/libraries/QuickSort.sol deleted file mode 100644 index fe841118a..000000000 --- a/contracts/libraries/QuickSort.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -library QuickSort { - function sort(uint256[] memory data) public view returns (uint256[] memory) { - return _quickSort(data, int256(0), int256(data.length - 1)); - } - - function _quickSort( - uint256[] memory arr, - int256 left, - int256 right - ) private view returns (uint256[] memory) { - int256 i = left; - int256 j = right; - if (i == j) return arr; - uint256 pivot = arr[uint256(left + (right - left) / 2)]; - while (i <= j) { - while (arr[uint256(i)] < pivot) i++; - while (pivot < arr[uint256(j)]) j--; - if (i <= j) { - (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]); - i++; - j--; - } - } - if (left < j) _quickSort(arr, left, j); - if (i < right) _quickSort(arr, i, right); - - return arr; - } -} diff --git a/contracts/libraries/Sorting.sol b/contracts/libraries/Sorting.sol new file mode 100644 index 000000000..12b4827e9 --- /dev/null +++ b/contracts/libraries/Sorting.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +library Sorting { + struct Node { + uint key; + uint value; + } + + function sort(uint[] memory data) public view returns (uint[] memory) { + return _quickSort(data, int(0), int(data.length - 1)); + } + + function sortNodes(Node[] memory nodes) public view returns (Node[] memory) { + return _quickSortNodes(nodes, int(0), int(nodes.length - 1)); + } + + function _quickSort( + uint[] memory arr, + int left, + int right + ) private view returns (uint[] memory) { + int i = left; + int j = right; + if (i == j) return arr; + uint pivot = arr[uint(left + (right - left) / 2)]; + while (i <= j) { + while (arr[uint(i)] < pivot) i++; + while (pivot < arr[uint(j)]) j--; + if (i <= j) { + (arr[uint(i)], arr[uint(j)]) = (arr[uint(j)], arr[uint(i)]); + i++; + j--; + } + } + if (left < j) _quickSort(arr, left, j); + if (i < right) _quickSort(arr, i, right); + + return arr; + } + + function _quickSortNodes( + Node[] memory nodes, + int left, + int right + ) private view returns (Node[] memory) { + int i = left; + int j = right; + if (i == j) return nodes; + Node memory pivot = nodes[uint(left + (right - left) / 2)]; + while (i <= j) { + while (nodes[uint(i)].value < pivot.value) i++; + while (pivot.value < nodes[uint(j)].value) j--; + if (i <= j) { + (nodes[uint(i)], nodes[uint(j)]) = __swapNodes(nodes[uint(i)], nodes[uint(j)]); + i++; + j--; + } + } + if (left < j) _quickSortNodes(nodes, left, j); + if (i < right) _quickSortNodes(nodes, i, right); + + return nodes; + } + + function __swapNodes(Node memory x, Node memory y) private pure returns (Node memory, Node memory) { + Node memory tmp; + (x, y) = (y, tmp); + return (x, y); + } +} From 737054102d12914dcb5306b740f3afc30589cb5b Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 30 Aug 2022 10:40:03 +0700 Subject: [PATCH 022/190] Add hardhat-gas-reporter package --- hardhat.config.ts | 6 +- package.json | 1 + yarn.lock | 775 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 746 insertions(+), 36 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 116ea5b61..d7f3945fc 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -2,6 +2,7 @@ import '@typechain/hardhat'; import '@nomiclabs/hardhat-waffle'; import '@nomiclabs/hardhat-ethers'; import 'hardhat-deploy'; +import 'hardhat-gas-reporter'; import * as dotenv from 'dotenv'; import { HardhatUserConfig, NetworkUserConfig, SolcUserConfig } from 'hardhat/types'; @@ -10,7 +11,7 @@ dotenv.config(); const DEFAULT_MNEMONIC = 'title spike pink garlic hamster sorry few damage silver mushroom clever window'; -const { TESTNET_PK, TESTNET_URL, MAINNET_PK, MAINNET_URL } = process.env; +const { REPORT_GAS, TESTNET_PK, TESTNET_URL, MAINNET_PK, MAINNET_URL } = process.env; if (!TESTNET_PK) { console.warn('TESTNET_PK is unset. Using DEFAULT_MNEMONIC'); @@ -67,6 +68,9 @@ const config: HardhatUserConfig = { 'ronin-testnet': testnet, 'ronin-mainnet': mainnet, }, + gasReporter: { + enabled: REPORT_GAS ? true : false + } }; export default config; diff --git a/package.json b/package.json index 5b2be75c5..73d9e77cc 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "ethers": "^5.5.2", "hardhat": "^2.7.1", "hardhat-deploy": "^0.9.14", + "hardhat-gas-reporter": "^1.0.8", "husky": "^7.0.4", "lint-staged": ">=10", "prettier": "^2.5.1", diff --git a/yarn.lock b/yarn.lock index 18f22af6c..fcc3f456b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -198,6 +198,21 @@ "@ethersproject/properties" "^5.6.0" "@ethersproject/strings" "^5.6.1" +"@ethersproject/abi@^5.0.0-beta.146": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" + integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/abstract-provider@5.6.1", "@ethersproject/abstract-provider@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.6.1.tgz#02ddce150785caf0c77fe036a0ebfcee61878c59" @@ -211,6 +226,19 @@ "@ethersproject/transactions" "^5.6.2" "@ethersproject/web" "^5.6.1" +"@ethersproject/abstract-provider@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" + integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + "@ethersproject/abstract-signer@5.6.2", "@ethersproject/abstract-signer@^5.4.1", "@ethersproject/abstract-signer@^5.6.2": version "5.6.2" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.6.2.tgz#491f07fc2cbd5da258f46ec539664713950b0b33" @@ -222,6 +250,17 @@ "@ethersproject/logger" "^5.6.0" "@ethersproject/properties" "^5.6.0" +"@ethersproject/abstract-signer@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" + integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/address@5.6.1", "@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@^5.4.0", "@ethersproject/address@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.6.1.tgz#ab57818d9aefee919c5721d28cd31fd95eff413d" @@ -233,6 +272,17 @@ "@ethersproject/logger" "^5.6.0" "@ethersproject/rlp" "^5.6.1" +"@ethersproject/address@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" + integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/base64@5.6.1", "@ethersproject/base64@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.6.1.tgz#2c40d8a0310c9d1606c2c37ae3092634b41d87cb" @@ -240,6 +290,13 @@ dependencies: "@ethersproject/bytes" "^5.6.1" +"@ethersproject/base64@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" + integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/basex@5.6.1", "@ethersproject/basex@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.6.1.tgz#badbb2f1d4a6f52ce41c9064f01eab19cc4c5305" @@ -257,6 +314,15 @@ "@ethersproject/logger" "^5.6.0" bn.js "^5.2.1" +"@ethersproject/bignumber@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" + integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + bn.js "^5.2.1" + "@ethersproject/bytes@5.6.1", "@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@^5.4.0", "@ethersproject/bytes@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.6.1.tgz#24f916e411f82a8a60412344bf4a813b917eefe7" @@ -264,6 +330,13 @@ dependencies: "@ethersproject/logger" "^5.6.0" +"@ethersproject/bytes@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" + integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== + dependencies: + "@ethersproject/logger" "^5.7.0" + "@ethersproject/constants@5.6.1", "@ethersproject/constants@>=5.0.0-beta.128", "@ethersproject/constants@^5.4.0", "@ethersproject/constants@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.6.1.tgz#e2e974cac160dd101cf79fdf879d7d18e8cb1370" @@ -271,6 +344,13 @@ dependencies: "@ethersproject/bignumber" "^5.6.2" +"@ethersproject/constants@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" + integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/contracts@5.6.2", "@ethersproject/contracts@^5.4.1": version "5.6.2" resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.6.2.tgz#20b52e69ebc1b74274ff8e3d4e508de971c287bc" @@ -301,6 +381,21 @@ "@ethersproject/properties" "^5.6.0" "@ethersproject/strings" "^5.6.1" +"@ethersproject/hash@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" + integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/hdnode@5.6.2", "@ethersproject/hdnode@^5.6.2": version "5.6.2" resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.6.2.tgz#26f3c83a3e8f1b7985c15d1db50dc2903418b2d2" @@ -346,11 +441,24 @@ "@ethersproject/bytes" "^5.6.1" js-sha3 "0.8.0" +"@ethersproject/keccak256@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + js-sha3 "0.8.0" + "@ethersproject/logger@5.6.0", "@ethersproject/logger@>=5.0.0-beta.129", "@ethersproject/logger@^5.6.0": version "5.6.0" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.6.0.tgz#d7db1bfcc22fd2e4ab574cba0bb6ad779a9a3e7a" integrity sha512-BiBWllUROH9w+P21RzoxJKzqoqpkyM1pRnEKG69bulE9TSQD8SAIvTQqIMZmmCO8pUNkgLP1wndX1gKghSpBmg== +"@ethersproject/logger@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" + integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== + "@ethersproject/networks@5.6.4", "@ethersproject/networks@^5.6.3": version "5.6.4" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.6.4.tgz#51296d8fec59e9627554f5a8a9c7791248c8dc07" @@ -358,6 +466,13 @@ dependencies: "@ethersproject/logger" "^5.6.0" +"@ethersproject/networks@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.0.tgz#df72a392f1a63a57f87210515695a31a245845ad" + integrity sha512-MG6oHSQHd4ebvJrleEQQ4HhVu8Ichr0RDYEfHzsVAVjHNM+w36x9wp9r+hf1JstMXtseXDtkiVoARAG6M959AA== + dependencies: + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2@5.6.1", "@ethersproject/pbkdf2@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.6.1.tgz#f462fe320b22c0d6b1d72a9920a3963b09eb82d1" @@ -373,6 +488,13 @@ dependencies: "@ethersproject/logger" "^5.6.0" +"@ethersproject/properties@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" + integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== + dependencies: + "@ethersproject/logger" "^5.7.0" + "@ethersproject/providers@5.6.8", "@ethersproject/providers@^5.4.4": version "5.6.8" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.6.8.tgz#22e6c57be215ba5545d3a46cf759d265bb4e879d" @@ -415,6 +537,14 @@ "@ethersproject/bytes" "^5.6.1" "@ethersproject/logger" "^5.6.0" +"@ethersproject/rlp@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" + integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/sha2@5.6.1", "@ethersproject/sha2@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.6.1.tgz#211f14d3f5da5301c8972a8827770b6fd3e51656" @@ -436,6 +566,18 @@ elliptic "6.5.4" hash.js "1.1.7" +"@ethersproject/signing-key@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" + integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + bn.js "^5.2.1" + elliptic "6.5.4" + hash.js "1.1.7" + "@ethersproject/solidity@5.6.1", "@ethersproject/solidity@^5.4.0": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.6.1.tgz#5845e71182c66d32e6ec5eefd041fca091a473e2" @@ -457,6 +599,15 @@ "@ethersproject/constants" "^5.6.1" "@ethersproject/logger" "^5.6.0" +"@ethersproject/strings@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" + integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/transactions@5.6.2", "@ethersproject/transactions@^5.0.0-beta.135", "@ethersproject/transactions@^5.4.0", "@ethersproject/transactions@^5.6.2": version "5.6.2" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.6.2.tgz#793a774c01ced9fe7073985bb95a4b4e57a6370b" @@ -472,6 +623,21 @@ "@ethersproject/rlp" "^5.6.1" "@ethersproject/signing-key" "^5.6.2" +"@ethersproject/transactions@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" + integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/units@5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.6.1.tgz#ecc590d16d37c8f9ef4e89e2005bda7ddc6a4e6f" @@ -513,6 +679,17 @@ "@ethersproject/properties" "^5.6.0" "@ethersproject/strings" "^5.6.1" +"@ethersproject/web@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.0.tgz#40850c05260edad8b54827923bbad23d96aac0bc" + integrity sha512-ApHcbbj+muRASVDSCl/tgxaH2LBkRMEYfLOLVa0COipx0+nlu0QKet7U2lEg0vdkh8XRSLf2nd1f1Uk9SrVSGA== + dependencies: + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/wordlists@5.6.1", "@ethersproject/wordlists@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.6.1.tgz#1e78e2740a8a21e9e99947e47979d72e130aeda1" @@ -828,7 +1005,7 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@solidity-parser/parser@^0.14.1", "@solidity-parser/parser@^0.14.2", "@solidity-parser/parser@^0.14.3": +"@solidity-parser/parser@^0.14.0", "@solidity-parser/parser@^0.14.1", "@solidity-parser/parser@^0.14.2", "@solidity-parser/parser@^0.14.3": version "0.14.3" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.3.tgz#0d627427b35a40d8521aaa933cc3df7d07bfa36f" integrity sha512-29g2SZ29HtsqA58pLCtopI1P/cPy5/UAzlcAXO6T/CNJimG6yA8kx4NaseMyJULiC+TEs02Y9/yeHzClqoA0hw== @@ -908,6 +1085,20 @@ resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.3.tgz#3c90752792660c4b562ad73b3fbd68bf3bc7ae07" integrity sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g== +"@types/concat-stream@^1.6.0": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@types/concat-stream/-/concat-stream-1.6.1.tgz#24bcfc101ecf68e886aaedce60dfd74b632a1b74" + integrity sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA== + dependencies: + "@types/node" "*" + +"@types/form-data@0.0.33": + version "0.0.33" + resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-0.0.33.tgz#c9ac85b2a5fd18435b8c85d9ecb50e6d6c893ff8" + integrity sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw== + dependencies: + "@types/node" "*" + "@types/level-errors@*": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/level-errors/-/level-errors-3.0.0.tgz#15c1f4915a5ef763b51651b15e90f6dc081b96a8" @@ -952,6 +1143,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.6.tgz#31743bc5772b6ac223845e18c3fc26f042713c83" integrity sha512-EdxgKRXgYsNITy5mjjXjVE/CS8YENSdhiagGrLqjG0pvA2owgJ6i4l7wy/PFZGC0B1/H20lWKN7ONVDNYDZm7A== +"@types/node@^10.0.3": + version "10.17.60" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" + integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== + "@types/node@^12.12.6": version "12.20.55" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" @@ -962,6 +1158,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== +"@types/node@^8.0.0": + version "8.10.66" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" + integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== + "@types/pbkdf2@^3.0.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.0.tgz#039a0e9b67da0cdc4ee5dab865caa6b267bb66b1" @@ -974,7 +1175,7 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.0.tgz#ea03e9f0376a4446f44797ca19d9c46c36e352dc" integrity sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A== -"@types/qs@^6.9.7": +"@types/qs@^6.2.31", "@types/qs@^6.9.7": version "6.9.7" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== @@ -1161,6 +1362,11 @@ ajv@^6.10.2, ajv@^6.12.3, ajv@^6.6.1, ajv@^6.9.1: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ansi-colors@3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" + integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== + ansi-colors@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" @@ -1242,7 +1448,7 @@ antlr4ts@^0.5.0-alpha.4: resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a" integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ== -anymatch@~3.1.2: +anymatch@~3.1.1, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -1316,6 +1522,11 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array-uniq@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== + array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" @@ -1332,6 +1543,11 @@ array.prototype.reduce@^1.0.4: es-array-method-boxes-properly "^1.0.0" is-string "^1.0.7" +asap@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + asn1.js@^5.2.0: version "5.4.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" @@ -2330,6 +2546,11 @@ camelcase@^3.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" integrity sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg== +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + camelcase@^6.0.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" @@ -2340,7 +2561,7 @@ caniuse-lite@^1.0.30000844: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001378.tgz#3d2159bf5a8f9ca093275b0d3ecc717b00f27b67" integrity sha512-JVQnfoO7FK7WvU4ZkBRbPjaot4+YqxogSDosHv0Hv5mWpUESmN+UubMU6L/hGz8QlQ2aY5U0vR6MOs6j/CXpNA== -caseless@~0.12.0: +caseless@^0.12.0, caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== @@ -2391,6 +2612,11 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +"charenc@>= 0.0.1": + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== + check-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" @@ -2403,6 +2629,21 @@ checkpoint-store@^1.1.0: dependencies: functional-red-black-tree "^1.0.1" +chokidar@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" + integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.2.0" + optionalDependencies: + fsevents "~2.1.1" + chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.2: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -2488,6 +2729,16 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" +cli-table3@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202" + integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw== + dependencies: + object-assign "^4.1.0" + string-width "^2.1.1" + optionalDependencies: + colors "^1.1.2" + cli-truncate@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" @@ -2518,6 +2769,15 @@ cliui@^3.2.0: strip-ansi "^3.0.1" wrap-ansi "^2.0.0" +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -2581,6 +2841,11 @@ colorette@^2.0.16, colorette@^2.0.17: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== +colors@1.4.0, colors@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -2652,7 +2917,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concat-stream@^1.5.1: +concat-stream@^1.5.1, concat-stream@^1.6.0, concat-stream@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -2822,6 +3087,11 @@ cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +"crypt@>= 0.0.1": + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== + crypto-browserify@3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -2882,7 +3152,7 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -decamelize@^1.1.1: +decamelize@^1.1.1, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== @@ -2961,7 +3231,7 @@ deferred-leveldown@~5.3.0: abstract-leveldown "~6.2.1" inherits "^2.0.3" -define-properties@^1.1.3, define-properties@^1.1.4: +define-properties@^1.1.2, define-properties@^1.1.3, define-properties@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== @@ -3026,6 +3296,11 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" +diff@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + diff@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" @@ -3287,16 +3562,16 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -3428,6 +3703,27 @@ eth-ens-namehash@2.0.8, eth-ens-namehash@^2.0.8: idna-uts46-hx "^2.3.1" js-sha3 "^0.5.7" +eth-gas-reporter@^0.2.24: + version "0.2.25" + resolved "https://registry.yarnpkg.com/eth-gas-reporter/-/eth-gas-reporter-0.2.25.tgz#546dfa946c1acee93cb1a94c2a1162292d6ff566" + integrity sha512-1fRgyE4xUB8SoqLgN3eDfpDfwEfRxh2Sz1b7wzFbyQA+9TekMmvSjjoRu9SKcSVyK+vLkLIsVbJDsTWjw195OQ== + dependencies: + "@ethersproject/abi" "^5.0.0-beta.146" + "@solidity-parser/parser" "^0.14.0" + cli-table3 "^0.5.0" + colors "1.4.0" + ethereum-cryptography "^1.0.3" + ethers "^4.0.40" + fs-readdir-recursive "^1.1.0" + lodash "^4.17.14" + markdown-table "^1.1.3" + mocha "^7.1.1" + req-cwd "^2.0.0" + request "^2.88.0" + request-promise-native "^1.0.5" + sha1 "^1.1.1" + sync-request "^6.0.0" + eth-json-rpc-infura@^3.1.0: version "3.2.1" resolved "https://registry.yarnpkg.com/eth-json-rpc-infura/-/eth-json-rpc-infura-3.2.1.tgz#26702a821067862b72d979c016fd611502c6057f" @@ -3797,6 +4093,21 @@ ethereumjs-wallet@0.6.5: utf8 "^3.0.0" uuid "^3.3.2" +ethers@^4.0.40: + version "4.0.49" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.49.tgz#0eb0e9161a0c8b4761be547396bbe2fb121a8894" + integrity sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg== + dependencies: + aes-js "3.0.0" + bn.js "^4.11.9" + elliptic "6.5.4" + hash.js "1.1.3" + js-sha3 "0.5.7" + scrypt-js "2.0.4" + setimmediate "1.0.4" + uuid "2.0.1" + xmlhttprequest "1.8.0" + ethers@^5.0.1, ethers@^5.0.2, ethers@^5.5.2: version "5.6.9" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.6.9.tgz#4e12f8dfcb67b88ae7a78a9519b384c23c576a4d" @@ -4108,6 +4419,13 @@ find-replace@^3.0.0: dependencies: array-back "^3.0.1" +find-up@3.0.0, find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + find-up@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -4155,6 +4473,13 @@ flat-cache@^2.0.1: rimraf "2.6.3" write "1.0.3" +flat@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.1.tgz#a392059cc382881ff98642f5da4dde0a959f309b" + integrity sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA== + dependencies: + is-buffer "~2.0.3" + flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" @@ -4199,6 +4524,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== +form-data@^2.2.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + form-data@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" @@ -4317,11 +4651,21 @@ fs-minipass@^1.2.7: dependencies: minipass "^2.6.0" +fs-readdir-recursive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" + integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@~2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== + fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" @@ -4394,7 +4738,7 @@ get-caller-file@^1.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== -get-caller-file@^2.0.5: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -4413,6 +4757,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.3" +get-port@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" + integrity sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg== + get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -4457,13 +4806,25 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob-parent@^5.1.2, glob-parent@~5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" +glob@7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" @@ -4560,6 +4921,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + handlebars@^4.7.6: version "4.7.7" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" @@ -4613,6 +4979,15 @@ hardhat-deploy@^0.9.14: murmur-128 "^0.2.1" qs "^6.9.4" +hardhat-gas-reporter@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.8.tgz#93ce271358cd748d9c4185dbb9d1d5525ec145e0" + integrity sha512-1G5thPnnhcwLHsFnl759f2tgElvuwdkzxlI65fC9PwxYMEe9cmjkVAAWTf3/3y8uP6ZSPiUiOW8PgZnykmZe0g== + dependencies: + array-uniq "1.0.3" + eth-gas-reporter "^0.2.24" + sha1 "^1.1.1" + hardhat@^2.7.1: version "2.10.2" resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.10.2.tgz#ff94ee4cb144a9c114641581ff5e4d7bea5f93a9" @@ -4701,7 +5076,7 @@ has-symbol-support-x@^1.4.1: resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== -has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: +has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== @@ -4767,6 +5142,14 @@ hash-base@^3.0.0: readable-stream "^3.6.0" safe-buffer "^5.2.0" +hash.js@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" + integrity sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.0" + hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" @@ -4807,6 +5190,16 @@ hosted-git-info@^2.1.4, hosted-git-info@^2.6.0: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== +http-basic@^8.1.1: + version "8.1.3" + resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-8.1.3.tgz#a7cabee7526869b9b710136970805b1004261bbf" + integrity sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw== + dependencies: + caseless "^0.12.0" + concat-stream "^1.6.2" + http-response-object "^3.0.1" + parse-cache-control "^1.0.1" + http-cache-semantics@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" @@ -4828,6 +5221,13 @@ http-https@^1.0.0: resolved "https://registry.yarnpkg.com/http-https/-/http-https-1.0.0.tgz#2f908dd5f1db4068c058cd6e6d4ce392c913389b" integrity sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg== +http-response-object@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-3.0.2.tgz#7f435bb210454e4360d074ef1f989d5ea8aa9810" + integrity sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA== + dependencies: + "@types/node" "^10.0.3" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -5056,6 +5456,11 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-buffer@~2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" @@ -5359,16 +5764,16 @@ isurl@^1.0.0-alpha5: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" +js-sha3@0.5.7, js-sha3@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" + integrity sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g== + js-sha3@0.8.0, js-sha3@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== -js-sha3@^0.5.7: - version "0.5.7" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" - integrity sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g== - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -5379,6 +5784,14 @@ js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" integrity sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg== +js-yaml@3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + js-yaml@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -5849,6 +6262,14 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -5871,11 +6292,18 @@ lodash@4.17.20: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== -lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4: +lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +log-symbols@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" + integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== + dependencies: + chalk "^2.4.2" + log-symbols@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" @@ -5981,6 +6409,11 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +markdown-table@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.3.tgz#9fcb69bcfdb8717bfd0398c6ec2d93036ef8de60" + integrity sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q== + match-all@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/match-all/-/match-all-1.2.6.tgz#66d276ad6b49655551e63d3a6ee53e8be0566f8d" @@ -6194,6 +6627,13 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== +minimatch@3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + minimatch@5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" @@ -6248,6 +6688,13 @@ mkdirp@*, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mkdirp@0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + mkdirp@^0.5.1, mkdirp@^0.5.5: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -6290,6 +6737,36 @@ mocha@^10.0.0: yargs-parser "20.2.4" yargs-unparser "2.0.0" +mocha@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.2.0.tgz#01cc227b00d875ab1eed03a75106689cfed5a604" + integrity sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ== + dependencies: + ansi-colors "3.2.3" + browser-stdout "1.3.1" + chokidar "3.3.0" + debug "3.2.6" + diff "3.5.0" + escape-string-regexp "1.0.5" + find-up "3.0.0" + glob "7.1.3" + growl "1.10.5" + he "1.2.0" + js-yaml "3.13.1" + log-symbols "3.0.0" + minimatch "3.0.4" + mkdirp "0.5.5" + ms "2.1.1" + node-environment-flags "1.0.6" + object.assign "4.1.0" + strip-json-comments "2.0.1" + supports-color "6.0.0" + which "1.3.1" + wide-align "1.1.3" + yargs "13.3.2" + yargs-parser "13.1.2" + yargs-unparser "1.6.0" + mock-fs@^4.1.0: version "4.14.0" resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18" @@ -6300,6 +6777,11 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -6421,6 +6903,14 @@ node-addon-api@^2.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== +node-environment-flags@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" + integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== + dependencies: + object.getownpropertydescriptors "^2.0.3" + semver "^5.7.0" + node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -6513,7 +7003,7 @@ object-is@^1.0.1: call-bind "^1.0.2" define-properties "^1.1.3" -object-keys@^1.1.1: +object-keys@^1.0.11, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== @@ -6530,6 +7020,16 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" +object.assign@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + object.assign@^4.1.2: version "4.1.4" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" @@ -6540,7 +7040,7 @@ object.assign@^4.1.2: has-symbols "^1.0.3" object-keys "^1.1.1" -object.getownpropertydescriptors@^2.1.1: +object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.1: version "2.1.4" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz#7965e6437a57278b587383831a9b829455a4bc37" integrity sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ== @@ -6663,6 +7163,13 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" +p-limit@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" @@ -6677,6 +7184,13 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + p-locate@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" @@ -6703,6 +7217,11 @@ p-try@^1.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -6721,6 +7240,11 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" +parse-cache-control@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-cache-control/-/parse-cache-control-1.0.1.tgz#8eeab3e54fa56920fe16ba38f77fa21aacc2d74e" + integrity sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg== + parse-headers@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9" @@ -6987,6 +7511,13 @@ promise-to-callback@^1.0.0: is-fn "^1.0.0" set-immediate-shim "^1.0.1" +promise@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" + integrity sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q== + dependencies: + asap "~2.0.6" + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -7100,7 +7631,7 @@ qs@6.10.3: dependencies: side-channel "^1.0.4" -qs@^6.7.0, qs@^6.9.4: +qs@^6.4.0, qs@^6.7.0, qs@^6.9.4: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== @@ -7220,6 +7751,13 @@ readable-stream@~1.0.15: isarray "0.0.1" string_decoder "~0.10.x" +readdirp@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" + integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== + dependencies: + picomatch "^2.0.4" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -7311,7 +7849,37 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request@^2.79.0, request@^2.85.0: +req-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/req-cwd/-/req-cwd-2.0.0.tgz#d4082b4d44598036640fb73ddea01ed53db49ebc" + integrity sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ== + dependencies: + req-from "^2.0.0" + +req-from@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/req-from/-/req-from-2.0.0.tgz#d74188e47f93796f4aa71df6ee35ae689f3e0e70" + integrity sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA== + dependencies: + resolve-from "^3.0.0" + +request-promise-core@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" + integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== + dependencies: + lodash "^4.17.19" + +request-promise-native@^1.0.5: + version "1.0.9" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" + integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== + dependencies: + request-promise-core "1.1.4" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.79.0, request@^2.85.0, request@^2.88.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -7357,6 +7925,11 @@ require-main-filename@^1.0.1: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" integrity sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug== +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" @@ -7529,6 +8102,11 @@ safe-regex@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +scrypt-js@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.4.tgz#32f8c5149f0797672e551c07e230f834b6af5f16" + integrity sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw== + scrypt-js@3.0.1, scrypt-js@^3.0.0, scrypt-js@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" @@ -7565,7 +8143,7 @@ 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@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -7654,6 +8232,11 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" +setimmediate@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.4.tgz#20e81de622d4a02588ce0c8da8973cbcf1d3138f" + integrity sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog== + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -7672,6 +8255,14 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" +sha1@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/sha1/-/sha1-1.1.1.tgz#addaa7a93168f393f19eb2b15091618e2700f848" + integrity sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA== + dependencies: + charenc ">= 0.0.1" + crypt ">= 0.0.1" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -8024,6 +8615,11 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g== + stream-to-pull-stream@^1.7.1: version "1.7.3" resolved "https://registry.yarnpkg.com/stream-to-pull-stream/-/stream-to-pull-stream-1.7.3.tgz#4161aa2d2eb9964de60bfa1af7feaf917e874ece" @@ -8056,7 +8652,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.1.0: +"string-width@^1.0.2 || 2", string-width@^2.1.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -8064,7 +8660,7 @@ string-width@^2.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string-width@^3.0.0: +string-width@^3.0.0, string-width@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== @@ -8151,7 +8747,7 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.1.0: +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== @@ -8191,15 +8787,22 @@ strip-hex-prefix@1.0.0: dependencies: is-hex-prefixed "1.0.0" +strip-json-comments@2.0.1, strip-json-comments@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + strip-json-comments@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strip-json-comments@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== +supports-color@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" + integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== + dependencies: + has-flag "^3.0.0" supports-color@8.1.1: version "8.1.1" @@ -8249,6 +8852,22 @@ swarm-js@^0.1.40: tar "^4.0.2" xhr-request "^1.0.1" +sync-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-6.1.0.tgz#e96217565b5e50bbffe179868ba75532fb597e68" + integrity sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw== + dependencies: + http-response-object "^3.0.1" + sync-rpc "^1.2.1" + then-request "^6.0.0" + +sync-rpc@^1.2.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/sync-rpc/-/sync-rpc-1.3.6.tgz#b2e8b2550a12ccbc71df8644810529deb68665a7" + integrity sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw== + dependencies: + get-port "^3.1.0" + table-layout@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04" @@ -8321,6 +8940,23 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +then-request@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/then-request/-/then-request-6.0.2.tgz#ec18dd8b5ca43aaee5cb92f7e4c1630e950d4f0c" + integrity sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA== + dependencies: + "@types/concat-stream" "^1.6.0" + "@types/form-data" "0.0.33" + "@types/node" "^8.0.0" + "@types/qs" "^6.2.31" + caseless "~0.12.0" + concat-stream "^1.6.0" + form-data "^2.2.0" + http-basic "^8.1.1" + http-response-object "^3.0.1" + promise "^8.0.0" + qs "^6.4.0" + through2@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" @@ -8400,7 +9036,7 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -tough-cookie@~2.5.0: +tough-cookie@^2.3.3, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== @@ -8787,6 +9423,11 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== +uuid@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.1.tgz#c2a30dedb3e535d72ccf82e343941a50ba8533ac" + integrity sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg== + uuid@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" @@ -9170,7 +9811,12 @@ which-module@^1.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" integrity sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ== -which@^1.2.9: +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== + +which@1.3.1, which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -9184,6 +9830,13 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wide-align@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + widest-line@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" @@ -9227,6 +9880,15 @@ wrap-ansi@^2.0.0: string-width "^1.0.1" strip-ansi "^3.0.1" +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -9320,6 +9982,11 @@ xhr@^2.0.4, xhr@^2.2.0, xhr@^2.3.3: parse-headers "^2.0.0" xtend "^4.0.0" +xmlhttprequest@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" + integrity sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA== + xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -9337,6 +10004,11 @@ y18n@^3.2.1: resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" @@ -9362,6 +10034,14 @@ yaml@^2.1.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.1.1.tgz#1e06fb4ca46e60d9da07e4f786ea370ed3c3cfec" integrity sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw== +yargs-parser@13.1.2, yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@20.2.4: version "20.2.4" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" @@ -9380,6 +10060,15 @@ yargs-parser@^20.2.2: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== +yargs-unparser@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" + integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== + dependencies: + flat "^4.1.0" + lodash "^4.17.15" + yargs "^13.3.0" + yargs-unparser@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" @@ -9390,6 +10079,22 @@ yargs-unparser@2.0.0: flat "^5.0.2" is-plain-obj "^2.1.0" +yargs@13.3.2, yargs@^13.3.0: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" + yargs@16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" From b6e8e94b7efda8d21dde4cf700ae1fbc7bddcc18 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 30 Aug 2022 13:14:58 +0700 Subject: [PATCH 023/190] [Slash] Add slash contract --- contracts/SlashIndicator.sol | 150 +++++++++++++++++++++++ contracts/interfaces/ISlashIndicator.sol | 18 ++- 2 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 contracts/SlashIndicator.sol diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol new file mode 100644 index 000000000..bd79748d3 --- /dev/null +++ b/contracts/SlashIndicator.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "./interfaces/ISlashIndicator.sol"; +import "./interfaces/IStaking.sol"; +import "./interfaces/IValidatorSet.sol"; + +contract SlashIndicator is ISlashIndicator { + enum SlashType { + UNKNOWN, + MISDEMAENOR, + FELONY, + DOUBLE_SIGNING + } + + /// Init configuration + uint256 public constant MISDEMEANOR_THRESHOLD = 50; + uint256 public constant FELONY_THRESHOLD = 150; + + /// State of the contract + address[] public validators; + mapping(address => Indicator) public indicators; + uint256 public previousHeight; + + /// Threshold of slashing + uint256 public misdemeanorThreshold; + uint256 public felonyThreshold; + + /// Other contract address + IValidatorSet public validatorSetContract; + IStaking public stakingContract; + + event SlashedValidator(address indexed validator, SlashType slashType); + event ResetIndicators(); + + modifier onlyCoinbase() { + require(msg.sender == block.coinbase, "Only coinbase"); + _; + } + + modifier onlyValidatorContract() { + require(msg.sender == address(validatorSetContract), "Only validator set contract"); + _; + } + + modifier oncePerBlock() { + require(block.number > previousHeight, "Cannot slash twice in one block"); + _; + previousHeight = block.number; + } + + constructor (IValidatorSet _validatorSetContract, IStaking _stakingContract) { + misdemeanorThreshold = MISDEMEANOR_THRESHOLD; + felonyThreshold = FELONY_THRESHOLD; + + validatorSetContract = _validatorSetContract; + stakingContract = _stakingContract; + } + + /** + * @notice Slash for inavailability + * + * @dev Increase the counter of validator with valAddr. If the counter passes the threshold, call + * the function from Validators.sol + * + * Requirements: + * - Only coinbase can call this method + * + */ + function slash(address _validatorAddr) external onlyCoinbase oncePerBlock { + // Check if the to be slashed validator is in the current epoch + require(validatorSetContract.isCurrentValidator(_validatorAddr), "Cannot slash validator not in current epoch"); + + Indicator storage indicator = indicators[_validatorAddr]; + + // Add the validator to the list if they are not exist yet + if (indicator.historicalCounter == 0) { + indicator.historicalCounter = 1; + indicator.counter = 1; + validators.push(_validatorAddr); + } else { + indicator.historicalCounter++; + indicator.counter++; + } + + indicator.height = block.number; + + // Slash the validator as either the fenoly or the misdemeanor + if (indicator.counter == felonyThreshold) { + indicator.counter = 0; + validatorSetContract.slashFelony(_validatorAddr); + emit SlashedValidator(_validatorAddr, SlashType.FELONY); + } else if (indicator.counter == misdemeanorThreshold) { + validatorSetContract.slashMisdemeanor(_validatorAddr); + emit SlashedValidator(_validatorAddr, SlashType.MISDEMAENOR); + } + } + + /** + * @dev Reset the counter of the validator everyday + * + * Requirements: + * - Only validator contract can call this method + */ + function resetCounters() external onlyValidatorContract { + if (validators.length == 0) { + return; + } + + for (uint i = 0; i < validators.length; i++) { + Indicator storage _indicator = indicators[validators[i]]; + _indicator.counter = 0; + } + + emit ResetIndicators(); + } + + /** + * @notice Slash for double signing + * + * @dev Verify the evidence, call the function from Validators.sol + * + * Requirements: + * - Only coinbase can call this method + * + */ + function slashDoubleSign(address valAddr, bytes calldata evidence) external onlyCoinbase { + revert("Not implemented"); + } + + /** + * @notice Get slash indicator of a validator + */ + function getSlashIndicator(address validator) external view returns (Indicator memory) { + Indicator memory _indicator = indicators[validator]; + return _indicator; + } + + /** + * @notice Get all validators which have slash information + */ + function getSlashValidators() external view returns (address[] memory) { + return validators; + } + + function getSlashThresholds() external view returns (uint256, uint256) { + return (misdemeanorThreshold, felonyThreshold); + } +} diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index a64e148af..3a0de505f 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -6,9 +6,10 @@ interface ISlashIndicator { struct Indicator { /// @dev The block height that the indicator get updated, make sure this update once each block uint256 height; - /// @dev Number of missed block the validator, should not be get decreased to keep track the - /// misbehavior records the validator. - uint256 counter; + /// @dev Number of missed block the validator, reset everyday or once reaching the fenoly threshold + uint128 counter; + /// @dev Tracking the misbehavior records the validator + uint128 historicalCounter; } /** @@ -27,9 +28,9 @@ interface ISlashIndicator { * @dev Reset the counter of the validator everyday * * Requirements: - * - Only validator can call this method + * - Only validator contract can call this method */ - function resetCounter() external; + function resetCounters() external; /** * @notice Slash for double signing @@ -49,5 +50,10 @@ interface ISlashIndicator { /** * @notice Get slash indicator of a validator */ - function getSlashIndicator(address validator) external view returns (uint256 height, uint256 counter); + function getSlashIndicator(address validator) external view returns (Indicator memory); + + /** + * @notice Get slash threshold + */ + function getSlashThresholds() external view returns (uint256 misdemeanorThreshold, uint256 felonyThreshold); } From 023af0f45539a7711ee34caa782d6a661cc0bf86 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 30 Aug 2022 13:57:31 +0700 Subject: [PATCH 024/190] Fix constructor for Slash and Validators contract --- contracts/SlashIndicator.sol | 19 +++++++++++++++---- contracts/ValidatorSet.sol | 7 +++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index bd79748d3..f68bec24f 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -19,6 +19,7 @@ contract SlashIndicator is ISlashIndicator { uint256 public constant FELONY_THRESHOLD = 150; /// State of the contract + bool public initialized; address[] public validators; mapping(address => Indicator) public indicators; uint256 public previousHeight; @@ -50,10 +51,20 @@ contract SlashIndicator is ISlashIndicator { previousHeight = block.number; } - constructor (IValidatorSet _validatorSetContract, IStaking _stakingContract) { + modifier onlyInitialized() { + require(initialized, "Contract is not initialized"); + _; + } + + constructor () { misdemeanorThreshold = MISDEMEANOR_THRESHOLD; felonyThreshold = FELONY_THRESHOLD; + } + + function initialize(IValidatorSet _validatorSetContract, IStaking _stakingContract) external { + require(!initialized, "Contract is already initialized"); + initialized = true; validatorSetContract = _validatorSetContract; stakingContract = _stakingContract; } @@ -68,7 +79,7 @@ contract SlashIndicator is ISlashIndicator { * - Only coinbase can call this method * */ - function slash(address _validatorAddr) external onlyCoinbase oncePerBlock { + function slash(address _validatorAddr) external onlyInitialized onlyCoinbase oncePerBlock { // Check if the to be slashed validator is in the current epoch require(validatorSetContract.isCurrentValidator(_validatorAddr), "Cannot slash validator not in current epoch"); @@ -103,7 +114,7 @@ contract SlashIndicator is ISlashIndicator { * Requirements: * - Only validator contract can call this method */ - function resetCounters() external onlyValidatorContract { + function resetCounters() external onlyInitialized onlyValidatorContract { if (validators.length == 0) { return; } @@ -125,7 +136,7 @@ contract SlashIndicator is ISlashIndicator { * - Only coinbase can call this method * */ - function slashDoubleSign(address valAddr, bytes calldata evidence) external onlyCoinbase { + function slashDoubleSign(address valAddr, bytes calldata evidence) external onlyInitialized onlyCoinbase { revert("Not implemented"); } diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index a9cecdafa..708eb524b 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -3,8 +3,9 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import "./interfaces/IValidatorSet.sol"; +import "./interfaces/ISlashIndicator.sol"; import "./interfaces/IStaking.sol"; +import "./interfaces/IValidatorSet.sol"; import "./ValidatorSetBase.sol"; /** @@ -22,6 +23,7 @@ contract ValidatorSet is ValidatorSetBase { uint256 public lastUpdated; IStaking stakingContract; + ISlashIndicator slashContract; event ValidatorSetUpdated(); event ValidatorJailed(address indexed validator); @@ -50,8 +52,9 @@ contract ValidatorSet is ValidatorSetBase { _; } - constructor(IStaking _stakingContract) { + constructor(IStaking _stakingContract, ISlashIndicator _slashContract) { stakingContract = _stakingContract; + slashContract = _slashContract; numOfCabinets = INIT_NUM_OF_CABINETS; epochLength = INIT_EPOCH_LENGTH; } From 64f5b9ed20feb842ac0b81a2f387a6bc960a257d Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 30 Aug 2022 18:17:51 +0700 Subject: [PATCH 025/190] [CoreStaking] add contract & test --- contracts/mocks/MockManager.sol | 139 +++++++++++++++ contracts/staking/CoreStaking.sol | 97 +++++++++++ test/staking/CoreStaking.test.ts | 270 ++++++++++++++++++++++++++++++ 3 files changed, 506 insertions(+) create mode 100644 contracts/mocks/MockManager.sol create mode 100644 contracts/staking/CoreStaking.sol create mode 100644 test/staking/CoreStaking.test.ts diff --git a/contracts/mocks/MockManager.sol b/contracts/mocks/MockManager.sol new file mode 100644 index 000000000..fcffb9afb --- /dev/null +++ b/contracts/mocks/MockManager.sol @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "hardhat/console.sol"; +import "../staking/CoreStaking.sol"; + +contract MockManager is CoreStaking { + /// @dev Mapping from user => staking balance + mapping(address => uint256) internal _stakingBalance; + /// @dev Mapping from epoch number => slashed + mapping(uint256 => bool) internal _epochSlashed; + + uint256[] internal _epoches; + + uint256 public totalBalance; + + StakingPool public pool; + StakingPoolSnapshot public poolSnapshot; + + constructor() { + _epoches.push(0); + } + + function endEpoch() external { + _epoches.push(block.number); + } + + function stake(address _user, uint256 _amount) external { + uint256 _balance = _stakingBalance[_user]; + uint256 _newBalance = _balance + _amount; + _syncUserInfo(_user, _newBalance); + _stakingBalance[_user] = _newBalance; + totalBalance += _amount; + } + + function unstake(address _user, uint256 _amount) external { + uint256 _balance = _stakingBalance[_user]; + uint256 _newBalance = _balance - _amount; + _syncUserInfo(_user, _newBalance); + _stakingBalance[_user] = _newBalance; + totalBalance -= _amount; + } + + function recordReward(uint256 _rewardAmount) public { + increaseAccumulatedRps((_rewardAmount * 1e18) / totalBalance); + } + + function commitRewardPool() public { + StakingPoolSnapshot storage _sPool = poolSnapshot; + _sPool.accumulatedRps = pool.accumulatedRps; + _sPool.lastSyncBlock = block.number; + } + + function increaseAccumulatedRps(uint256 _amount) public { + pool.accumulatedRps += _amount; + pool.lastSyncBlock = block.number; + } + + function increaseAndSyncSnapshot(uint256 _amount) external { + increaseAccumulatedRps(_amount); + poolSnapshot.accumulatedRps = pool.accumulatedRps; + poolSnapshot.lastSyncBlock = block.number; + } + + function slash() external { + uint256 _epoch = getEpoch(); + console.log("Slash block=", block.number, "at epoch=", _epoch); + _epochSlashed[_epoch] = true; + pool.accumulatedRps = poolSnapshot.accumulatedRps; + pool.lastSyncBlock = block.number; + } + + function getEpoch() public view returns (uint256) { + return _blockNumberToEpoch(block.number); + } + + function claimReward(address _user) public returns (uint256 _amount) { + uint256 _balance = getCurrentBalance(_user); + _amount = getClaimableReward(_user); + StakingPoolSnapshot memory _sPool = _getStakingPoolSnapshot(); + console.log("User", _user, "claimed", _amount); + + UserRewardInfo storage _reward = _getUserRewardInfo(_user); + console.log("claimReward: \t =>", _reward.debited); + _reward.debited = 0; + _reward.credited = (_balance * _sPool.accumulatedRps) / 1e18; + _reward.lastSyncBlock = block.number; + console.log("claimReward: \t", _balance, _sPool.accumulatedRps); + + UserRewardInfoSnapshot storage _sReward = _getUserRewardInfoSnapshot(_user); + _sReward.debited = 0; + _sReward.accumulatedRps = _sPool.accumulatedRps; + } + + function getClaimableReward(address _user) public view returns (uint256) { + UserRewardInfo memory _reward = _getUserRewardInfo(_user); + UserRewardInfoSnapshot memory _sReward = _getUserRewardInfoSnapshot(_user); + StakingPoolSnapshot memory _sPool = _getStakingPoolSnapshot(); + + console.log("-> getClaimableReward", _reward.lastSyncBlock, _sPool.lastSyncBlock); + if (_reward.lastSyncBlock <= _sPool.lastSyncBlock) { + console.log("\t-> sync"); + _sReward.debited = (getCurrentBalance(_user) * _sPool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; + _sReward.balance = getCurrentBalance(_user); + _sReward.accumulatedRps = _sPool.accumulatedRps; + } + + uint256 _balance = _sReward.balance; + uint256 _credited = (_balance * _sReward.accumulatedRps) / 1e18; + console.log("\t", _balance, _sReward.accumulatedRps, _sPool.accumulatedRps); + console.log("\t", _sReward.debited); + return (_balance * _sPool.accumulatedRps) / 1e18 + _sReward.debited - _credited; + } + + function _slashed(StakingPool memory, uint256 _epoch) internal view override returns (bool) { + return _epochSlashed[_epoch]; + } + + function _blockNumberToEpoch(uint256 _block) internal view override returns (uint256 _epoch) { + for (uint256 _i; _i < _epoches.length; _i++) { + if (_block >= _epoches[_i]) { + _epoch = _i + 1; + } + } + } + + function _getStakingPool() internal view override returns (StakingPool memory) { + return pool; + } + + function _getStakingPoolSnapshot() internal view override returns (StakingPoolSnapshot memory) { + return poolSnapshot; + } + + function getCurrentBalance(address _user) public view override returns (uint256) { + return _stakingBalance[_user]; + } +} diff --git a/contracts/staking/CoreStaking.sol b/contracts/staking/CoreStaking.sol new file mode 100644 index 000000000..02a88b09e --- /dev/null +++ b/contracts/staking/CoreStaking.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "hardhat/console.sol"; + +abstract contract CoreStaking { + struct UserRewardInfo { + uint256 debited; // + + uint256 credited; // - + uint256 lastSyncBlock; + } + + struct UserRewardInfoSnapshot { + uint256 balance; + uint256 debited; // + + /// @dev Accumulated of the amount rewards per share (one unit staking). + uint256 accumulatedRps; + } + + struct StakingPool { + uint256 lastSyncBlock; + /// @dev Accumulated of the amount rewards per share (one unit staking). + uint256 accumulatedRps; + } + + struct StakingPoolSnapshot { + uint256 lastSyncBlock; + /// @dev Accumulated of the amount rewards per share (one unit staking). + uint256 accumulatedRps; + } + + mapping(address => UserRewardInfoSnapshot) internal _sUserReward; + mapping(address => UserRewardInfo) internal _userReward; + + function _syncUserInfo(address _user, uint256 _newBalance) internal { + UserRewardInfo storage _reward = _getUserRewardInfo(_user); + StakingPoolSnapshot memory _sPool = _getStakingPoolSnapshot(); + + uint256 _lastUserReward = getUserReward(_user); + // Syncs the user reward snapshot once the last sync is committed (snapshotted). + if (_reward.lastSyncBlock <= _sPool.lastSyncBlock) { + UserRewardInfoSnapshot storage _sReward = _getUserRewardInfoSnapshot(_user); + console.log("Synced for", _user, _lastUserReward, _sPool.accumulatedRps); + _sReward.balance = getCurrentBalance(_user); + _sReward.debited = _lastUserReward; + _sReward.accumulatedRps = _sPool.accumulatedRps; + } + + StakingPool memory _pool = _getStakingPool(); + _reward.debited = _lastUserReward; + _reward.credited = (_newBalance * _pool.accumulatedRps) / 1e18; + _reward.lastSyncBlock = block.number; + } + + function getUserReward(address _user) public view returns (uint256) { + UserRewardInfo memory _reward = _getUserRewardInfo(_user); + StakingPool memory _pool = _getStakingPool(); + + uint256 _balance = getCurrentBalance(_user); + if (_slashed(_pool, _blockNumberToEpoch(_reward.lastSyncBlock))) { + UserRewardInfoSnapshot memory _sReward = _getUserRewardInfoSnapshot(_user); + return _getUserRewardOnSlashed(_sReward, _pool.accumulatedRps, _balance); + } + + console.log("getUserReward:\t", _balance, _pool.accumulatedRps, _reward.debited); + console.log("getUserReward:\t", _reward.credited); + return (_balance * _pool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; + } + + function _getUserRewardOnSlashed( + UserRewardInfoSnapshot memory _sReward, + uint256 _accumulatedRps, + uint256 _balance + ) internal pure returns (uint256) { + uint256 _credited = (_sReward.accumulatedRps * _balance) / 1e18; + return (_balance * _accumulatedRps) / 1e18 + _sReward.debited - _credited; + } + + function _getUserRewardInfo(address _user) internal view virtual returns (UserRewardInfo storage) { + return _userReward[_user]; + } + + function _getUserRewardInfoSnapshot(address _user) internal view virtual returns (UserRewardInfoSnapshot storage) { + return _sUserReward[_user]; + } + + function _slashed(StakingPool memory, uint256) internal view virtual returns (bool) {} + + function _blockNumberToEpoch(uint256) internal view virtual returns (uint256) {} + + function _getStakingPool() internal view virtual returns (StakingPool memory) {} + + function _getStakingPoolSnapshot() internal view virtual returns (StakingPoolSnapshot memory) {} + + function getCurrentBalance(address) public view virtual returns (uint256) {} +} diff --git a/test/staking/CoreStaking.test.ts b/test/staking/CoreStaking.test.ts new file mode 100644 index 000000000..67d4fde9d --- /dev/null +++ b/test/staking/CoreStaking.test.ts @@ -0,0 +1,270 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { expect } from 'chai'; +import { BigNumber, BigNumberish } from 'ethers'; +import { ethers, network } from 'hardhat'; + +import { MockManager, MockManager__factory } from '../../src/types'; + +const EPS = 1; + +let deployer: SignerWithAddress; +let userA: SignerWithAddress; +let userB: SignerWithAddress; +let manager: MockManager; + +const local = { + accumulatedRewardForA: BigNumber.from(0), + accumulatedRewardForB: BigNumber.from(0), + claimableRewardForA: BigNumber.from(0), + claimableRewardForB: BigNumber.from(0), + recordReward: async function (reward: BigNumberish) { + const totalStaked = await manager.totalBalance(); + const stakingAmountA = await manager.getCurrentBalance(userA.address); + const stakingAmountB = await manager.getCurrentBalance(userB.address); + this.accumulatedRewardForA = this.accumulatedRewardForA.add( + BigNumber.from(reward).mul(stakingAmountA).div(totalStaked) + ); + this.accumulatedRewardForB = this.accumulatedRewardForB.add( + BigNumber.from(reward).mul(stakingAmountB).div(totalStaked) + ); + }, + commitRewardPool: function () { + this.claimableRewardForA = this.accumulatedRewardForA; + this.claimableRewardForB = this.accumulatedRewardForB; + }, + slash: function () { + this.accumulatedRewardForA = this.claimableRewardForA; + this.accumulatedRewardForB = this.claimableRewardForB; + }, + reset: function () { + this.claimableRewardForA = BigNumber.from(0); + this.claimableRewardForB = BigNumber.from(0); + this.accumulatedRewardForA = BigNumber.from(0); + this.accumulatedRewardForB = BigNumber.from(0); + }, + claimRewardForA: function () { + this.accumulatedRewardForA = this.accumulatedRewardForA.sub(this.claimableRewardForA); + this.claimableRewardForA = BigNumber.from(0); + }, + claimRewardForB: function () { + this.accumulatedRewardForB = this.accumulatedRewardForB.sub(this.claimableRewardForB); + this.claimableRewardForB = BigNumber.from(0); + }, +}; + +const expectLocalCalculationRight = async () => { + { + const userReward = await manager.getUserReward(userA.address); + expect( + userReward.sub(local.accumulatedRewardForA).abs().lte(EPS), + `invalid user reward for A expected=${local.accumulatedRewardForA.toString()} actual=${userReward}` + ).to.be.true; + const claimableReward = await manager.getClaimableReward(userA.address); + expect( + claimableReward.sub(local.claimableRewardForA).abs().lte(EPS), + `invalid claimable reward for A expected=${local.claimableRewardForA.toString()} actual=${claimableReward}` + ).to.be.true; + } + { + const userReward = await manager.getUserReward(userB.address); + expect( + userReward.sub(local.accumulatedRewardForB).abs().lte(EPS), + `invalid user reward for B expected=${local.accumulatedRewardForB.toString()} actual=${userReward}` + ).to.be.true; + const claimableReward = await manager.getClaimableReward(userB.address); + expect( + claimableReward.sub(local.claimableRewardForB).abs().lte(EPS), + `invalid claimable reward for B expected=${local.claimableRewardForB.toString()} actual=${claimableReward}` + ).to.be.true; + } +}; + +describe('Core Staking test', () => { + describe('Total reward calculation', async () => { + before(async () => { + [deployer, userA, userB] = await ethers.getSigners(); + manager = await new MockManager__factory(deployer).deploy(); + await network.provider.send('evm_setAutomine', [false]); + local.reset(); + }); + + it('Should be able to stake/unstake and get right total reward for a successful epoch', async () => { + await manager.stake(userA.address, 100); + await manager.stake(userB.address, 100); + await network.provider.send('evm_mine'); + + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.stake(userA.address, 200); + await network.provider.send('evm_mine'); + await expectLocalCalculationRight(); + + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.unstake(userA.address, 200); + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.stake(userA.address, 200); + await network.provider.send('evm_mine'); + await local.recordReward(0); + await expectLocalCalculationRight(); + + await manager.commitRewardPool(); + await manager.endEpoch(); + await network.provider.send('evm_mine'); + local.commitRewardPool(); + await expectLocalCalculationRight(); + }); + + it('Should be able to stake/unstake and get right total reward for a slashed epoch', async () => { + await manager.stake(userA.address, 100); + await network.provider.send('evm_mine'); + await expectLocalCalculationRight(); + await local.recordReward(0); + + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.stake(userA.address, 300); + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.slash(); + await network.provider.send('evm_mine'); + local.slash(); + await expectLocalCalculationRight(); + + await manager.recordReward(0); + await network.provider.send('evm_mine'); + await local.recordReward(0); + await expectLocalCalculationRight(); + + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + + await manager.unstake(userA.address, 300); + await network.provider.send('evm_mine'); + await manager.unstake(userA.address, 100); + await network.provider.send('evm_mine'); + await expectLocalCalculationRight(); + + await manager.commitRewardPool(); + await manager.endEpoch(); + await network.provider.send('evm_mine'); + await expectLocalCalculationRight(); + }); + + it('Should be able to stake/unstake/claim and get right total reward for a slashed epoch again', async () => { + await manager.stake(userA.address, 100); + await network.provider.send('evm_mine'); + await local.recordReward(0); + await expectLocalCalculationRight(); + + await manager.claimReward(userA.address); + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + local.claimRewardForA(); + await expectLocalCalculationRight(); + + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.stake(userA.address, 300); + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.slash(); + await network.provider.send('evm_mine'); + local.slash(); + await expectLocalCalculationRight(); + + await manager.recordReward(0); + await network.provider.send('evm_mine'); + await local.recordReward(0); + await expectLocalCalculationRight(); + + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + + await manager.unstake(userA.address, 300); + await network.provider.send('evm_mine'); + await manager.unstake(userA.address, 100); + await network.provider.send('evm_mine'); + await expectLocalCalculationRight(); + + await manager.claimReward(userB.address); + await manager.commitRewardPool(); + await manager.endEpoch(); + await network.provider.send('evm_mine'); + local.claimRewardForB(); + await expectLocalCalculationRight(); + }); + + it('Should be able to get right claimable reward for the committed epoch', async () => { + await manager.recordReward(1000); + await manager.commitRewardPool(); + await manager.endEpoch(); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + local.commitRewardPool(); + await expectLocalCalculationRight(); + console.log(local); + console.log('================='); + + await manager.claimReward(userA.address); + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + local.claimRewardForA(); + console.log(local, await ethers.provider.getBlockNumber()); + await expectLocalCalculationRight(); + console.log('================='); + + await manager.claimReward(userA.address); + await network.provider.send('evm_mine'); + local.claimRewardForA(); + console.log(local, await ethers.provider.getBlockNumber()); + await expectLocalCalculationRight(); + console.log('================='); + + await manager.claimReward(userB.address); + await network.provider.send('evm_mine'); + local.claimRewardForB(); + await expectLocalCalculationRight(); + }); + }); +}); From 6f797cd76dcc8b256ddb35e020a0193b5329baea Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 30 Aug 2022 14:15:22 +0700 Subject: [PATCH 026/190] Rename. Fix error messages. --- contracts/SlashIndicator.sol | 12 ++++++------ contracts/ValidatorSet.sol | 14 +++++++------- .../{ValidatorSetBase.sol => ValidatorSetCore.sol} | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) rename contracts/{ValidatorSetBase.sol => ValidatorSetCore.sol} (98%) diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index f68bec24f..3af15917c 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -36,23 +36,23 @@ contract SlashIndicator is ISlashIndicator { event ResetIndicators(); modifier onlyCoinbase() { - require(msg.sender == block.coinbase, "Only coinbase"); + require(msg.sender == block.coinbase, "Slash: Only coinbase"); _; } modifier onlyValidatorContract() { - require(msg.sender == address(validatorSetContract), "Only validator set contract"); + require(msg.sender == address(validatorSetContract), "Slash: Only validator set contract"); _; } modifier oncePerBlock() { - require(block.number > previousHeight, "Cannot slash twice in one block"); + require(block.number > previousHeight, "Slash: Cannot slash twice in one block"); _; previousHeight = block.number; } modifier onlyInitialized() { - require(initialized, "Contract is not initialized"); + require(initialized, "Slash: Contract is not initialized"); _; } @@ -62,7 +62,7 @@ contract SlashIndicator is ISlashIndicator { } function initialize(IValidatorSet _validatorSetContract, IStaking _stakingContract) external { - require(!initialized, "Contract is already initialized"); + require(!initialized, "Slash: Contract is already initialized"); initialized = true; validatorSetContract = _validatorSetContract; @@ -81,7 +81,7 @@ contract SlashIndicator is ISlashIndicator { */ function slash(address _validatorAddr) external onlyInitialized onlyCoinbase oncePerBlock { // Check if the to be slashed validator is in the current epoch - require(validatorSetContract.isCurrentValidator(_validatorAddr), "Cannot slash validator not in current epoch"); + require(validatorSetContract.isCurrentValidator(_validatorAddr), "Slash: Cannot slash validator not in current epoch"); Indicator storage indicator = indicators[_validatorAddr]; diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index 708eb524b..e6f035b74 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -6,13 +6,13 @@ import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "./interfaces/ISlashIndicator.sol"; import "./interfaces/IStaking.sol"; import "./interfaces/IValidatorSet.sol"; -import "./ValidatorSetBase.sol"; +import "./ValidatorSetCore.sol"; /** * @title Set of validators in current epoch * @notice This contract maintains set of validator in the current epoch of Ronin network */ -contract ValidatorSet is ValidatorSetBase { +contract ValidatorSet is ValidatorSetCore { using EnumerableMap for EnumerableMap.AddressToUintMap; uint256 private constant INIT_NUM_OF_CABINETS = 21; @@ -33,22 +33,22 @@ contract ValidatorSet is ValidatorSetBase { event DeprecatedDeposit(address indexed validator, uint256 amount); modifier onlyCoinbase() { - require(tx.origin == block.coinbase, "Only coinbase"); + require(tx.origin == block.coinbase, "Validators: Only coinbase"); _; } modifier onlyValidator() { - require(isValidator(msg.sender), "Only validator"); + require(isValidator(msg.sender), "Validators: Only validator"); _; } modifier noEmptyDeposit() { - require(msg.value > 0, "No empty deposit"); + require(msg.value > 0, "Validators: No empty deposit"); _; } modifier atEpochEnding() { - require((block.number + 1) % epochLength == 0, "Only at the end of the epoch"); + require((block.number + 1) % epochLength == 0, "Validators: Only at the end of the epoch"); _; } @@ -62,7 +62,7 @@ contract ValidatorSet is ValidatorSetBase { function updateValidators() external onlyValidator atEpochEnding returns (address[] memory) { // 1. fetch new validator set from staking contract IStaking.ValidatorCandidate[] memory _upcommingValidatorSet = stakingContract.updateValidatorSet(); - require(_upcommingValidatorSet.length <= numOfCabinets, "Exceeds maximum validators per epoch"); + require(_upcommingValidatorSet.length <= numOfCabinets, "Validators: Exceeds maximum validators per epoch"); // 2. update last updated lastUpdated = block.number; diff --git a/contracts/ValidatorSetBase.sol b/contracts/ValidatorSetCore.sol similarity index 98% rename from contracts/ValidatorSetBase.sol rename to contracts/ValidatorSetCore.sol index 8c7a29967..6374c229b 100644 --- a/contracts/ValidatorSetBase.sol +++ b/contracts/ValidatorSetCore.sol @@ -10,7 +10,7 @@ import "./interfaces/IStaking.sol"; * @title Set of validators in current epoch * @notice This contract maintains set of validator in the current epoch of Ronin network */ -abstract contract ValidatorSetBase is IValidatorSet { +abstract contract ValidatorSetCore is IValidatorSet { using EnumerableMap for EnumerableMap.AddressToUintMap; /// @dev Array of all validators. The element at 0-slot is reserved for unknown validator. From 366eed92a78679d6792c8495b8787de81657b559 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 30 Aug 2022 15:39:55 +0700 Subject: [PATCH 027/190] [Validator] Refactor. Lint. Add mock. --- contracts/ValidatorSet.sol | 2 +- contracts/ValidatorSetCore.sol | 30 +++++++++------ contracts/mocks/ValidatorSetCoreMock.sol | 48 ++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 13 deletions(-) create mode 100644 contracts/mocks/ValidatorSetCoreMock.sol diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index e6f035b74..ba0dad596 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -12,7 +12,7 @@ import "./ValidatorSetCore.sol"; * @title Set of validators in current epoch * @notice This contract maintains set of validator in the current epoch of Ronin network */ -contract ValidatorSet is ValidatorSetCore { +contract ValidatorSet is IValidatorSet, ValidatorSetCore { using EnumerableMap for EnumerableMap.AddressToUintMap; uint256 private constant INIT_NUM_OF_CABINETS = 21; diff --git a/contracts/ValidatorSetCore.sol b/contracts/ValidatorSetCore.sol index 6374c229b..664ab8111 100644 --- a/contracts/ValidatorSetCore.sol +++ b/contracts/ValidatorSetCore.sol @@ -10,11 +10,11 @@ import "./interfaces/IStaking.sol"; * @title Set of validators in current epoch * @notice This contract maintains set of validator in the current epoch of Ronin network */ -abstract contract ValidatorSetCore is IValidatorSet { +abstract contract ValidatorSetCore { using EnumerableMap for EnumerableMap.AddressToUintMap; /// @dev Array of all validators. The element at 0-slot is reserved for unknown validator. - Validator[] public validatorSet; + IValidatorSet.Validator[] public validatorSet; /// @dev Map of array of all validator mapping(address => uint256) public validatorSetMap; /// @dev Enumerable map of indexes of the current validator set, e.g. the array [3, 4, 1] tells @@ -23,7 +23,7 @@ abstract contract ValidatorSetCore is IValidatorSet { /// indexed from 0, but its content is non-zero value, due to reserved 0-slot in `validatorSet`. EnumerableMap.AddressToUintMap internal currentValidatorIndexesMap; - constructor () { + constructor() { // Add empty validator at 0-slot for the set map validatorSet.push(); } @@ -45,7 +45,9 @@ abstract contract ValidatorSetCore is IValidatorSet { internal returns (uint256) { - (bool _success, Validator storage _currentValidator) = _tryGetValidator(_incomingValidator.consensusAddr); + (bool _success, IValidatorSet.Validator storage _currentValidator) = _tryGetValidator( + _incomingValidator.consensusAddr + ); if (_success && _forced) { return __setExistedValidator(_incomingValidator, _currentValidator); } @@ -57,34 +59,38 @@ abstract contract ValidatorSetCore is IValidatorSet { currentValidatorIndexesMap.remove(_oldValAddr); } - function _getValidatorAtMiningIndex(uint256 _miningIndex) internal view returns (Validator storage) { + function _getValidatorAtMiningIndex(uint256 _miningIndex) internal view returns (IValidatorSet.Validator storage) { (, uint256 _index) = currentValidatorIndexesMap.at(_miningIndex); require(_index != 0, string(abi.encodePacked("Nonexistent validator at index ", _miningIndex))); return validatorSet[_index]; } - function _getValidator(address _valAddr) internal view returns (Validator storage) { - (bool _success, Validator storage _v) = _tryGetValidator(_valAddr); + function _getValidator(address _valAddr) internal view returns (IValidatorSet.Validator storage) { + (bool _success, IValidatorSet.Validator storage _v) = _tryGetValidator(_valAddr); require(_success, string(abi.encodePacked("Nonexistent validator ", _valAddr))); return _v; } - function _tryGetValidator(address _valAddr) internal view returns (bool, Validator storage) { + function _tryGetValidator(address _valAddr) internal view returns (bool, IValidatorSet.Validator storage) { uint256 _index = validatorSetMap[_valAddr]; - Validator storage _v = validatorSet[_index]; + IValidatorSet.Validator storage _v = validatorSet[_index]; if (_index == 0) { return (false, _v); } return (true, _v); } - function _isSameValidator(IStaking.ValidatorCandidate memory _v1, Validator memory _v2) internal pure returns (bool) { + function _isSameValidator(IStaking.ValidatorCandidate memory _v1, IValidatorSet.Validator memory _v2) + internal + pure + returns (bool) + { return _v1.consensusAddr == _v2.consensusAddr && _v1.treasuryAddr == _v2.treasuryAddr; } function __setExistedValidator( IStaking.ValidatorCandidate memory _incomingValidator, - Validator storage _currentValidator + IValidatorSet.Validator storage _currentValidator ) private returns (uint256) { _currentValidator.consensusAddr = _incomingValidator.consensusAddr; _currentValidator.treasuryAddr = _incomingValidator.treasuryAddr; @@ -96,7 +102,7 @@ abstract contract ValidatorSetCore is IValidatorSet { uint256 index = validatorSet.length; validatorSetMap[_incomingValidator.consensusAddr] = index; - Validator storage _v = validatorSet.push(); + IValidatorSet.Validator storage _v = validatorSet.push(); _v.consensusAddr = _incomingValidator.consensusAddr; _v.treasuryAddr = _incomingValidator.treasuryAddr; diff --git a/contracts/mocks/ValidatorSetCoreMock.sol b/contracts/mocks/ValidatorSetCoreMock.sol new file mode 100644 index 000000000..5fb22ef48 --- /dev/null +++ b/contracts/mocks/ValidatorSetCoreMock.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../ValidatorSetCore.sol"; + +/** + * @title Set of validators in current epoch + * @notice This contract maintains set of validator in the current epoch of Ronin network + */ +contract ValidatorSetCoreMock is ValidatorSetCore { + function setValidatorAtMiningIndex(uint256 _miningIndex, IStaking.ValidatorCandidate memory _incomingValidator) + external + { + _setValidatorAtMiningIndex(_miningIndex, _incomingValidator); + } + + function setValidator(IStaking.ValidatorCandidate memory _incomingValidator, bool _forced) + external + returns (uint256) + { + return _setValidator(_incomingValidator, _forced); + } + + function removeValidatorAtMiningIndex(uint256 _miningIndex) external { + _removeValidatorAtMiningIndex(_miningIndex); + } + + function getValidatorAtMiningIndex(uint256 _miningIndex) external view returns (IValidatorSet.Validator memory) { + return _getValidatorAtMiningIndex(_miningIndex); + } + + function getValidator(address _valAddr) external view returns (IValidatorSet.Validator memory) { + return _getValidator(_valAddr); + } + + function tryGetValidator(address _valAddr) external view returns (bool, IValidatorSet.Validator memory) { + return _tryGetValidator(_valAddr); + } + + function isSameValidator(IStaking.ValidatorCandidate memory _v1, IValidatorSet.Validator memory _v2) + external + pure + returns (bool) + { + return _isSameValidator(_v1, _v2); + } +} From c539c2b18e0d356a2eabce59324ea10afa4e1260 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 30 Aug 2022 22:31:38 +0700 Subject: [PATCH 028/190] [Validator] Fix ValidatorCore. Add test. --- contracts/ValidatorSetCore.sol | 56 +++++--- ...tCoreMock.sol => MockValidatorSetCore.sol} | 4 +- src/addresses.ts | 9 ++ src/utils.ts | 11 ++ tests/validators/ValidatorSetCore.test.ts | 127 ++++++++++++++++++ 5 files changed, 189 insertions(+), 18 deletions(-) rename contracts/mocks/{ValidatorSetCoreMock.sol => MockValidatorSetCore.sol} (92%) create mode 100644 src/addresses.ts create mode 100644 src/utils.ts create mode 100644 tests/validators/ValidatorSetCore.test.ts diff --git a/contracts/ValidatorSetCore.sol b/contracts/ValidatorSetCore.sol index 664ab8111..898c8f2d2 100644 --- a/contracts/ValidatorSetCore.sol +++ b/contracts/ValidatorSetCore.sol @@ -15,7 +15,7 @@ abstract contract ValidatorSetCore { /// @dev Array of all validators. The element at 0-slot is reserved for unknown validator. IValidatorSet.Validator[] public validatorSet; - /// @dev Map of array of all validator + /// @dev Map of array of all validators mapping(address => uint256) public validatorSetMap; /// @dev Enumerable map of indexes of the current validator set, e.g. the array [3, 4, 1] tells /// the sequence of validators to mine block in the next epoch will be at the 3-, 4- and 1-index, @@ -33,25 +33,40 @@ abstract contract ValidatorSetCore { { address _newValAddr = _incomingValidator.consensusAddr; uint256 _index = _setValidator(_incomingValidator, false); + uint256 _indexesLength = currentValidatorIndexesMap.length(); - (address _oldValAddr, ) = currentValidatorIndexesMap.at(_miningIndex); - if (_oldValAddr != _newValAddr) { - currentValidatorIndexesMap.remove(_oldValAddr); - currentValidatorIndexesMap.set(_newValAddr, _index); + + require(_miningIndex <= _indexesLength, "Cannot set mining index greater than current indexes array length"); + if (_miningIndex < _indexesLength) { + (address _oldValAddr, ) = currentValidatorIndexesMap.at(_miningIndex); + if (_oldValAddr != _newValAddr) { + currentValidatorIndexesMap.remove(_oldValAddr); + } } + + currentValidatorIndexesMap.set(_newValAddr, _index); } - function _setValidator(IStaking.ValidatorCandidate memory _incomingValidator, bool _forced) + /** + * @return validatorIndex_ Actual index of the validator in the `validatorSet` + */ + function _setValidator(IStaking.ValidatorCandidate memory _incomingValidator, bool _forcedIfExist) internal - returns (uint256) + returns (uint256 validatorIndex_) { - (bool _success, IValidatorSet.Validator storage _currentValidator) = _tryGetValidator( + (bool _success, IValidatorSet.Validator storage _currentValidator, uint256 _actualIndex) = _tryGetValidator( _incomingValidator.consensusAddr ); - if (_success && _forced) { - return __setExistedValidator(_incomingValidator, _currentValidator); + + if (!_success) { + return __setUnexistentValidator(_incomingValidator); + } else { + if (_forcedIfExist) { + return __setExistedValidator(_incomingValidator, _currentValidator); + } else { + return _actualIndex; + } } - return __setUnexistentValidator(_incomingValidator); } function _removeValidatorAtMiningIndex(uint256 _miningIndex) internal { @@ -60,24 +75,33 @@ abstract contract ValidatorSetCore { } function _getValidatorAtMiningIndex(uint256 _miningIndex) internal view returns (IValidatorSet.Validator storage) { + require(_miningIndex < currentValidatorIndexesMap.length(), "No validator exists at queried mining index"); (, uint256 _index) = currentValidatorIndexesMap.at(_miningIndex); - require(_index != 0, string(abi.encodePacked("Nonexistent validator at index ", _miningIndex))); + require(_index != 0, "No validator exists at mining index 0"); return validatorSet[_index]; } function _getValidator(address _valAddr) internal view returns (IValidatorSet.Validator storage) { - (bool _success, IValidatorSet.Validator storage _v) = _tryGetValidator(_valAddr); + (bool _success, IValidatorSet.Validator storage _v, ) = _tryGetValidator(_valAddr); require(_success, string(abi.encodePacked("Nonexistent validator ", _valAddr))); return _v; } - function _tryGetValidator(address _valAddr) internal view returns (bool, IValidatorSet.Validator storage) { + function _tryGetValidator(address _valAddr) + internal + view + returns ( + bool, + IValidatorSet.Validator storage, + uint256 + ) + { uint256 _index = validatorSetMap[_valAddr]; IValidatorSet.Validator storage _v = validatorSet[_index]; if (_index == 0) { - return (false, _v); + return (false, _v, 0); } - return (true, _v); + return (true, _v, _index); } function _isSameValidator(IStaking.ValidatorCandidate memory _v1, IValidatorSet.Validator memory _v2) diff --git a/contracts/mocks/ValidatorSetCoreMock.sol b/contracts/mocks/MockValidatorSetCore.sol similarity index 92% rename from contracts/mocks/ValidatorSetCoreMock.sol rename to contracts/mocks/MockValidatorSetCore.sol index 5fb22ef48..e5baa766d 100644 --- a/contracts/mocks/ValidatorSetCoreMock.sol +++ b/contracts/mocks/MockValidatorSetCore.sol @@ -8,7 +8,7 @@ import "../ValidatorSetCore.sol"; * @title Set of validators in current epoch * @notice This contract maintains set of validator in the current epoch of Ronin network */ -contract ValidatorSetCoreMock is ValidatorSetCore { +contract MockValidatorSetCore is ValidatorSetCore { function setValidatorAtMiningIndex(uint256 _miningIndex, IStaking.ValidatorCandidate memory _incomingValidator) external { @@ -34,7 +34,7 @@ contract ValidatorSetCoreMock is ValidatorSetCore { return _getValidator(_valAddr); } - function tryGetValidator(address _valAddr) external view returns (bool, IValidatorSet.Validator memory) { + function tryGetValidator(address _valAddr) external view returns (bool, IValidatorSet.Validator memory, uint256) { return _tryGetValidator(_valAddr); } diff --git a/src/addresses.ts b/src/addresses.ts new file mode 100644 index 000000000..aa55933c8 --- /dev/null +++ b/src/addresses.ts @@ -0,0 +1,9 @@ +export enum Network { + Hardhat = 'hardhat', + Testnet = 'ronin-testnet', + Mainnet = 'ronin-mainnet', +} + +export type LiteralNetwork = Network | string; + +export const defaultAddress = '0x0000000000000000000000000000000000000000'; \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 000000000..e733e8453 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,11 @@ +import { BigNumber } from 'ethers'; + +export const DEFAULT_ADDRESS = '0x0000000000000000000000000000000000000000'; +export const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000'; + +export const randomBigNumber = () => { + const hexString = Array.from(Array(64)) + .map(() => Math.round(Math.random() * 0xf).toString(16)) + .join(''); + return BigNumber.from(`0x${hexString}`); +}; diff --git a/tests/validators/ValidatorSetCore.test.ts b/tests/validators/ValidatorSetCore.test.ts new file mode 100644 index 000000000..ee27951cd --- /dev/null +++ b/tests/validators/ValidatorSetCore.test.ts @@ -0,0 +1,127 @@ +import { expect } from 'chai'; +import { deployments, ethers } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; + +import { + ValidatorSetCore, + ValidatorSetCore__factory, + MockValidatorSetCore, + MockValidatorSetCore__factory, +} from '../../src/types'; +import { ValidatorStruct, ValidatorCandidateStruct } from '../../src/types/ValidatorSetCoreMock'; +import { BigNumber } from 'ethers/lib/ethers'; +import { DEFAULT_ADDRESS } from '../../src/utils'; + +let validatorsCore: MockValidatorSetCore; + +let admin: SignerWithAddress; +let s1: SignerWithAddress; +let s2: SignerWithAddress; +let s3: SignerWithAddress; +let s4: SignerWithAddress; +let s5: SignerWithAddress; +let s6: SignerWithAddress; +let v1: SignerWithAddress; +let v2: SignerWithAddress; +let v3: SignerWithAddress; +let v4: SignerWithAddress; +let v5: SignerWithAddress; +let v6: SignerWithAddress; +let candidate1: ValidatorCandidateStruct; +let candidate2: ValidatorCandidateStruct; +let candidate3: ValidatorCandidateStruct; +let candidate4: ValidatorCandidateStruct; +let candidate5: ValidatorCandidateStruct; +let candidate6: ValidatorCandidateStruct; + +const generateCandidate = (stakingAddr: string, consensusAddr: string): ValidatorCandidateStruct => { + return { + stakingAddr: stakingAddr, + consensusAddr: consensusAddr, + treasuryAddr: DEFAULT_ADDRESS, + commissionRate: 0, + stakedAmount: 0, + delegatedAmount: 0, + governing: false, + ____gap: Array.apply(null, Array(20)).map((_) => 0), + }; +}; + +describe('Validator set core tests', () => { + describe('CRUD functions on validator set', async () => { + before(async () => { + [admin, s1, s2, s3, s4, s5, s6, v1, v2, v3, v4, v5, v6] = await ethers.getSigners(); + validatorsCore = await new MockValidatorSetCore__factory(admin).deploy(); + + candidate1 = generateCandidate(s1.address, v1.address); + candidate2 = generateCandidate(s2.address, v2.address); + candidate3 = generateCandidate(s3.address, v3.address); + candidate4 = generateCandidate(s4.address, v4.address); + candidate5 = generateCandidate(s5.address, v5.address); + candidate6 = generateCandidate(s6.address, v6.address); + }); + + describe('Simple CRUD', async () => { + it('Should be able to set one validator (set unexisted validator)', async () => { + await validatorsCore.setValidator(candidate1, false); + + let validator = await validatorsCore.getValidator(v1.address); + expect(candidate1.consensusAddr).eq(validator.consensusAddr); + }); + + it('Should added validator not be in the current mining index', async () => { + let miningValidator = validatorsCore.getValidatorAtMiningIndex(0); + await expect(miningValidator).to.revertedWith('No validator exists at queried mining index'); + }); + + it('Should be able set the added validator at 0 mining slot (skipping add new validator on actual set)', async () => { + await validatorsCore.setValidatorAtMiningIndex(0, candidate1); + + let miningValidator = await validatorsCore.getValidatorAtMiningIndex(0); + await expect(miningValidator.consensusAddr).eq(candidate1.consensusAddr); + }); + + it('Should not be able to retrieve validator at 1-index while having 1 validator in the list', async () => { + await expect(validatorsCore.getValidatorAtMiningIndex(1)).to.revertedWith( + 'No validator exists at queried mining index' + ); + }); + + it('Should be able to add a new validator at 1-slot while having 1 validator in the list', async () => { + let miningValidator = await validatorsCore.getValidatorAtMiningIndex(0); + await expect(miningValidator.consensusAddr).eq(candidate1.consensusAddr); + + await validatorsCore.setValidatorAtMiningIndex(1, candidate2); + + miningValidator = await validatorsCore.getValidatorAtMiningIndex(1); + await expect(miningValidator.consensusAddr).eq(candidate2.consensusAddr); + }); + + it('Should not be able to add a new validator at 3-slot while having 2 validator in the list', async () => { + await expect(validatorsCore.setValidatorAtMiningIndex(3, candidate3)).to.revertedWith( + 'Cannot set mining index greater than current indexes array length' + ); + }); + + it('Should be able to swap the validator', async () => { + await validatorsCore.setValidator(candidate6, true); + await validatorsCore.setValidatorAtMiningIndex(1, candidate6); + + let miningValidator = await validatorsCore.getValidatorAtMiningIndex(1); + await expect(miningValidator.consensusAddr).eq(candidate6.consensusAddr); + + await expect(validatorsCore.getValidatorAtMiningIndex(2)).to.revertedWith( + 'No validator exists at queried mining index' + ); + }); + + it('Should be able to remove the validator', async () => { + await validatorsCore.removeValidatorAtMiningIndex(1); + + await expect(validatorsCore.getValidatorAtMiningIndex(1)).to.revertedWith( + 'No validator exists at queried mining index' + ); + }); + }); + }); +}); From d5b9c121812f443dcf09ab5805b2e8669fc9396b Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 30 Aug 2022 22:31:55 +0700 Subject: [PATCH 029/190] Add hardhat-gas-reporter config --- .env.example | 5 ++++- hardhat.config.ts | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index d8f3bdf35..e11050397 100644 --- a/.env.example +++ b/.env.example @@ -4,4 +4,7 @@ TESTNET_URL=https://testnet.skymavis.one/rpc # Mainnet configs MAINNET_PK=0x0000000000000000000000000000000000000000000000000000000000000000 -MAINNET_URL=https://api.roninchain.com/rpc \ No newline at end of file +MAINNET_URL=https://api.roninchain.com/rpc + +# Report gas +REPORT_GAS=false \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index d7f3945fc..a17920f97 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -69,7 +69,8 @@ const config: HardhatUserConfig = { 'ronin-mainnet': mainnet, }, gasReporter: { - enabled: REPORT_GAS ? true : false + enabled: REPORT_GAS ? true : false, + showTimeSpent: true } }; From 840b3adac79e50b14792e2fea7612bc92c555fa9 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 30 Aug 2022 22:32:05 +0700 Subject: [PATCH 030/190] chore: lint --- contracts/Staking.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/Staking.sol b/contracts/Staking.sol index 592e44711..ed8c4c5b7 100644 --- a/contracts/Staking.sol +++ b/contracts/Staking.sol @@ -234,7 +234,7 @@ abstract contract Staking is IStaking, Initializable { * * Requirements: * - Only validator and `ValidatorSet` contract can call this function - * + * * @return newValidatorSet Validator set for the new epoch */ function updateValidatorSet() external returns (ValidatorCandidate[] memory newValidatorSet) { @@ -263,7 +263,7 @@ abstract contract Staking is IStaking, Initializable { function getCurrentValidatorSet() public view returns (ValidatorCandidate[] memory currentValidatorSet_) { uint _length = currentValidatorIndexes.length; - currentValidatorSet_ = new ValidatorCandidate[](currentValidatorIndexes.length); + currentValidatorSet_ = new ValidatorCandidate[](currentValidatorIndexes.length); for (uint i = 0; i < _length; i++) { currentValidatorSet_[i] = validatorCandidates[currentValidatorIndexes[i]]; } From 24e88eb9afdd2eb7d54fd853bd861f5783fc87b3 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Wed, 31 Aug 2022 01:41:15 +0700 Subject: [PATCH 031/190] [Validator] Fix bug in core contract. Update test. --- contracts/ValidatorSet.sol | 10 +- contracts/ValidatorSetCore.sol | 87 +++++---- contracts/mocks/MockValidatorSetCore.sol | 13 +- hardhat.config.ts | 1 + tests/validators/ValidatorSetCore.test.ts | 221 ++++++++++++++-------- 5 files changed, 213 insertions(+), 119 deletions(-) diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index ba0dad596..6f656bc67 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -85,7 +85,7 @@ contract ValidatorSet is IValidatorSet, ValidatorSetCore { Validator storage _validator = _getValidator(_valAddr); // 1. check if validator is in current epoch - if (currentValidatorIndexesMap.contains(_valAddr)) { + if (_isInCurrentValidatorSet(_valAddr)) { // 2. check if validator is in jail if (_validator.jailed) { emit DeprecatedDeposit(_valAddr, _value); @@ -121,7 +121,7 @@ contract ValidatorSet is IValidatorSet, ValidatorSetCore { } function getValidators() public view returns (address[] memory) { - uint256 size = currentValidatorIndexesMap.length(); + uint256 size = _getCurrentValidatorSetSize(); address[] memory validatorAddresses = new address[](size); for (uint256 i = 0; i < size; i++) { Validator memory _v = _getValidatorAtMiningIndex(i); @@ -140,7 +140,7 @@ contract ValidatorSet is IValidatorSet, ValidatorSetCore { } function isCurrentValidator(address _addr) public view returns (bool) { - return currentValidatorIndexesMap.contains(_addr); + return _isInCurrentValidatorSet(_addr); } /// @@ -148,7 +148,7 @@ contract ValidatorSet is IValidatorSet, ValidatorSetCore { /// function _doUpdateState(IStaking.ValidatorCandidate[] memory _incomingValidatorSet) private { - uint256 n = currentValidatorIndexesMap.length(); + uint256 n = _getCurrentValidatorSetSize(); uint256 m = _incomingValidatorSet.length; uint256 k = n < m ? n : m; @@ -179,7 +179,7 @@ contract ValidatorSet is IValidatorSet, ValidatorSetCore { // updating part: ^^^^^^ if (n > m) { for (uint256 i = m; i < n; ++i) { - _removeValidatorAtMiningIndex(i); + _popValidatorFromMiningIndex(); } } } diff --git a/contracts/ValidatorSetCore.sol b/contracts/ValidatorSetCore.sol index 898c8f2d2..67b702d03 100644 --- a/contracts/ValidatorSetCore.sol +++ b/contracts/ValidatorSetCore.sol @@ -16,35 +16,44 @@ abstract contract ValidatorSetCore { /// @dev Array of all validators. The element at 0-slot is reserved for unknown validator. IValidatorSet.Validator[] public validatorSet; /// @dev Map of array of all validators - mapping(address => uint256) public validatorSetMap; - /// @dev Enumerable map of indexes of the current validator set, e.g. the array [3, 4, 1] tells + mapping(address => uint) public validatorSetMap; + /// @dev Enumerable map of indexes of the current validator set, e.g. the array [0, 3, 4, 1] tells /// the sequence of validators to mine block in the next epoch will be at the 3-, 4- and 1-index, /// One can query the mining order of an arbitrary address by this enumerable map. This array is - /// indexed from 0, but its content is non-zero value, due to reserved 0-slot in `validatorSet`. - EnumerableMap.AddressToUintMap internal currentValidatorIndexesMap; + /// indexed from 1, and its content is non-zero value, due to reserved 0-slot in `validatorSet`. + uint[] internal currentValidatorIndexes; + mapping(address => uint) currentValidatorIndexesMap; constructor() { // Add empty validator at 0-slot for the set map validatorSet.push(); + currentValidatorIndexes.push(); } - function _setValidatorAtMiningIndex(uint256 _miningIndex, IStaking.ValidatorCandidate memory _incomingValidator) + function _isInCurrentValidatorSet(address _addr) internal view returns (bool) { + return (currentValidatorIndexesMap[_addr] != 0); + } + + function _getCurrentValidatorSetSize() internal view returns (uint) { + return currentValidatorIndexes.length - 1; + } + + function _setValidatorAtMiningIndex(uint _miningIndex, IStaking.ValidatorCandidate memory _incomingValidator) internal { - address _newValAddr = _incomingValidator.consensusAddr; - uint256 _index = _setValidator(_incomingValidator, false); - uint256 _indexesLength = currentValidatorIndexesMap.length(); + require(_miningIndex > 0, "Validator: Cannot set at 0-index of mining set"); + uint _length = currentValidatorIndexes.length; + require(_miningIndex <= _length, "Validator: Cannot set at out-of-bound mining set"); + address _newValAddr = _incomingValidator.consensusAddr; + uint _incomingIndex = _setValidator(_incomingValidator, false); - require(_miningIndex <= _indexesLength, "Cannot set mining index greater than current indexes array length"); - if (_miningIndex < _indexesLength) { - (address _oldValAddr, ) = currentValidatorIndexesMap.at(_miningIndex); - if (_oldValAddr != _newValAddr) { - currentValidatorIndexesMap.remove(_oldValAddr); - } + currentValidatorIndexesMap[_newValAddr] = _miningIndex; + if (_miningIndex < _length) { + currentValidatorIndexes[_miningIndex] = _incomingIndex; + } else { + currentValidatorIndexes.push(_incomingIndex); } - - currentValidatorIndexesMap.set(_newValAddr, _index); } /** @@ -52,9 +61,9 @@ abstract contract ValidatorSetCore { */ function _setValidator(IStaking.ValidatorCandidate memory _incomingValidator, bool _forcedIfExist) internal - returns (uint256 validatorIndex_) + returns (uint validatorIndex_) { - (bool _success, IValidatorSet.Validator storage _currentValidator, uint256 _actualIndex) = _tryGetValidator( + (bool _success, IValidatorSet.Validator storage _currentValidator, uint _actualIndex) = _tryGetValidator( _incomingValidator.consensusAddr ); @@ -69,21 +78,25 @@ abstract contract ValidatorSetCore { } } - function _removeValidatorAtMiningIndex(uint256 _miningIndex) internal { - (address _oldValAddr, ) = currentValidatorIndexesMap.at(_miningIndex); - currentValidatorIndexesMap.remove(_oldValAddr); + function _popValidatorFromMiningIndex() internal { + uint _length = currentValidatorIndexes.length - 1; + require(_length > 1, "Validator: Cannot remove the last element"); + + IValidatorSet.Validator storage _lastValidatorInEpoch = validatorSet[currentValidatorIndexes[_length]]; + currentValidatorIndexesMap[_lastValidatorInEpoch.consensusAddr] = 0; + currentValidatorIndexes.pop(); } - function _getValidatorAtMiningIndex(uint256 _miningIndex) internal view returns (IValidatorSet.Validator storage) { - require(_miningIndex < currentValidatorIndexesMap.length(), "No validator exists at queried mining index"); - (, uint256 _index) = currentValidatorIndexesMap.at(_miningIndex); - require(_index != 0, "No validator exists at mining index 0"); - return validatorSet[_index]; + function _getValidatorAtMiningIndex(uint _miningIndex) internal view returns (IValidatorSet.Validator storage) { + require(_miningIndex < currentValidatorIndexes.length, "Validator: No validator exists at queried mining index"); + uint _actualIndex = currentValidatorIndexes[_miningIndex]; + require(_actualIndex != 0, "Validator: No validator exists at mining index 0"); + return validatorSet[_actualIndex]; } function _getValidator(address _valAddr) internal view returns (IValidatorSet.Validator storage) { (bool _success, IValidatorSet.Validator storage _v, ) = _tryGetValidator(_valAddr); - require(_success, string(abi.encodePacked("Nonexistent validator ", _valAddr))); + require(_success, string(abi.encodePacked("Validator: Nonexistent validator ", _valAddr))); return _v; } @@ -93,15 +106,15 @@ abstract contract ValidatorSetCore { returns ( bool, IValidatorSet.Validator storage, - uint256 + uint ) { - uint256 _index = validatorSetMap[_valAddr]; - IValidatorSet.Validator storage _v = validatorSet[_index]; - if (_index == 0) { + uint _actualIndex = validatorSetMap[_valAddr]; + IValidatorSet.Validator storage _v = validatorSet[_actualIndex]; + if (_actualIndex == 0) { return (false, _v, 0); } - return (true, _v, _index); + return (true, _v, _actualIndex); } function _isSameValidator(IStaking.ValidatorCandidate memory _v1, IValidatorSet.Validator memory _v2) @@ -115,21 +128,21 @@ abstract contract ValidatorSetCore { function __setExistedValidator( IStaking.ValidatorCandidate memory _incomingValidator, IValidatorSet.Validator storage _currentValidator - ) private returns (uint256) { + ) private returns (uint) { _currentValidator.consensusAddr = _incomingValidator.consensusAddr; _currentValidator.treasuryAddr = _incomingValidator.treasuryAddr; return validatorSetMap[_currentValidator.consensusAddr]; } - function __setUnexistentValidator(IStaking.ValidatorCandidate memory _incomingValidator) private returns (uint256) { - uint256 index = validatorSet.length; + function __setUnexistentValidator(IStaking.ValidatorCandidate memory _incomingValidator) private returns (uint) { + uint _actualIndex = validatorSet.length; - validatorSetMap[_incomingValidator.consensusAddr] = index; + validatorSetMap[_incomingValidator.consensusAddr] = _actualIndex; IValidatorSet.Validator storage _v = validatorSet.push(); _v.consensusAddr = _incomingValidator.consensusAddr; _v.treasuryAddr = _incomingValidator.treasuryAddr; - return index; + return _actualIndex; } } diff --git a/contracts/mocks/MockValidatorSetCore.sol b/contracts/mocks/MockValidatorSetCore.sol index e5baa766d..268775449 100644 --- a/contracts/mocks/MockValidatorSetCore.sol +++ b/contracts/mocks/MockValidatorSetCore.sol @@ -9,6 +9,15 @@ import "../ValidatorSetCore.sol"; * @notice This contract maintains set of validator in the current epoch of Ronin network */ contract MockValidatorSetCore is ValidatorSetCore { + + function isInCurrentValidatorSet(address _addr) external view returns (bool) { + return _isInCurrentValidatorSet(_addr); + } + + function getCurrentValidatorSetSize() external view returns (uint256) { + return _getCurrentValidatorSetSize(); + } + function setValidatorAtMiningIndex(uint256 _miningIndex, IStaking.ValidatorCandidate memory _incomingValidator) external { @@ -22,8 +31,8 @@ contract MockValidatorSetCore is ValidatorSetCore { return _setValidator(_incomingValidator, _forced); } - function removeValidatorAtMiningIndex(uint256 _miningIndex) external { - _removeValidatorAtMiningIndex(_miningIndex); + function popValidatorFromMiningIndex() external { + _popValidatorFromMiningIndex(); } function getValidatorAtMiningIndex(uint256 _miningIndex) external view returns (IValidatorSet.Validator memory) { diff --git a/hardhat.config.ts b/hardhat.config.ts index a17920f97..2d3d8ec39 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -63,6 +63,7 @@ const config: HardhatUserConfig = { hardhat: { accounts: { mnemonic: DEFAULT_MNEMONIC, + count: 100 }, }, 'ronin-testnet': testnet, diff --git a/tests/validators/ValidatorSetCore.test.ts b/tests/validators/ValidatorSetCore.test.ts index ee27951cd..7d90319b3 100644 --- a/tests/validators/ValidatorSetCore.test.ts +++ b/tests/validators/ValidatorSetCore.test.ts @@ -1,44 +1,28 @@ import { expect } from 'chai'; -import { deployments, ethers } from 'hardhat'; +import { ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { - ValidatorSetCore, - ValidatorSetCore__factory, MockValidatorSetCore, MockValidatorSetCore__factory, } from '../../src/types'; -import { ValidatorStruct, ValidatorCandidateStruct } from '../../src/types/ValidatorSetCoreMock'; -import { BigNumber } from 'ethers/lib/ethers'; -import { DEFAULT_ADDRESS } from '../../src/utils'; +import { ValidatorCandidateStruct } from '../../src/types/ValidatorSetCoreMock'; +import { randomSigners } from '../../src/utils'; let validatorsCore: MockValidatorSetCore; +let signers: SignerWithAddress[]; +let candidates: ValidatorCandidateStruct[]; let admin: SignerWithAddress; -let s1: SignerWithAddress; -let s2: SignerWithAddress; -let s3: SignerWithAddress; -let s4: SignerWithAddress; -let s5: SignerWithAddress; -let s6: SignerWithAddress; -let v1: SignerWithAddress; -let v2: SignerWithAddress; -let v3: SignerWithAddress; -let v4: SignerWithAddress; -let v5: SignerWithAddress; -let v6: SignerWithAddress; -let candidate1: ValidatorCandidateStruct; -let candidate2: ValidatorCandidateStruct; -let candidate3: ValidatorCandidateStruct; -let candidate4: ValidatorCandidateStruct; -let candidate5: ValidatorCandidateStruct; -let candidate6: ValidatorCandidateStruct; - -const generateCandidate = (stakingAddr: string, consensusAddr: string): ValidatorCandidateStruct => { +let stakingAddrs: SignerWithAddress[]; +let consensusAddrs: SignerWithAddress[]; +let treasuryAddrs: SignerWithAddress[]; + +const generateCandidate = (stakingAddr: string, consensusAddr: string, treasuryAddr: string): ValidatorCandidateStruct => { return { stakingAddr: stakingAddr, consensusAddr: consensusAddr, - treasuryAddr: DEFAULT_ADDRESS, + treasuryAddr: treasuryAddr, commissionRate: 0, stakedAmount: 0, delegatedAmount: 0, @@ -49,78 +33,165 @@ const generateCandidate = (stakingAddr: string, consensusAddr: string): Validato describe('Validator set core tests', () => { describe('CRUD functions on validator set', async () => { - before(async () => { - [admin, s1, s2, s3, s4, s5, s6, v1, v2, v3, v4, v5, v6] = await ethers.getSigners(); - validatorsCore = await new MockValidatorSetCore__factory(admin).deploy(); - - candidate1 = generateCandidate(s1.address, v1.address); - candidate2 = generateCandidate(s2.address, v2.address); - candidate3 = generateCandidate(s3.address, v3.address); - candidate4 = generateCandidate(s4.address, v4.address); - candidate5 = generateCandidate(s5.address, v5.address); - candidate6 = generateCandidate(s6.address, v6.address); - }); - describe('Simple CRUD', async () => { + before(async () => { + [admin, ...signers] = await ethers.getSigners(); + validatorsCore = await new MockValidatorSetCore__factory(admin).deploy(); + + candidates = []; + stakingAddrs = []; + consensusAddrs = []; + treasuryAddrs = []; + + for (let i = 0; i < 7; i++) { + candidates.push(generateCandidate(signers[3*i].address, signers[3*i+1].address, signers[3*i+2].address)); + stakingAddrs.push(signers[3*i]); + consensusAddrs.push(signers[3*i + 1]); + treasuryAddrs.push(signers[3*i + 2]); + } + }); + it('Should be able to set one validator (set unexisted validator)', async () => { - await validatorsCore.setValidator(candidate1, false); + await validatorsCore.setValidator(candidates[1], false); + let validator = await validatorsCore.getValidator(consensusAddrs[1].address); + expect(candidates[1].consensusAddr).eq(validator.consensusAddr); + expect(candidates[1].treasuryAddr).eq(validator.treasuryAddr); + }); - let validator = await validatorsCore.getValidator(v1.address); - expect(candidate1.consensusAddr).eq(validator.consensusAddr); + it('Should be able to set one validator (skip to set existed validator, not forced)', async () => { + await validatorsCore.setValidator(candidates[1], false); + let validator = await validatorsCore.getValidator(consensusAddrs[1].address); + expect(candidates[1].consensusAddr).eq(validator.consensusAddr); + expect(candidates[1].treasuryAddr).eq(validator.treasuryAddr); }); - it('Should added validator not be in the current mining index', async () => { - let miningValidator = validatorsCore.getValidatorAtMiningIndex(0); - await expect(miningValidator).to.revertedWith('No validator exists at queried mining index'); + it('Should be able to set one validator (set existed validator, forced, to update treasury address)', async () => { + candidates[1].treasuryAddr = treasuryAddrs[5].address; + await validatorsCore.setValidator(candidates[1], true); + let validator = await validatorsCore.getValidator(consensusAddrs[1].address); + expect(candidates[1].consensusAddr).eq(validator.consensusAddr); + expect(treasuryAddrs[5].address).eq(validator.treasuryAddr); }); - it('Should be able set the added validator at 0 mining slot (skipping add new validator on actual set)', async () => { - await validatorsCore.setValidatorAtMiningIndex(0, candidate1); + it('Should fail to query in mining validator set', async () => { + let miningValidator = validatorsCore.getValidatorAtMiningIndex(1); + await expect(miningValidator).to.revertedWith('Validator: No validator exists at queried mining index'); + }); - let miningValidator = await validatorsCore.getValidatorAtMiningIndex(0); - await expect(miningValidator.consensusAddr).eq(candidate1.consensusAddr); + it('Should be able set the added validator at 1-index slot (skipping add new validator on actual set)', async () => { + await validatorsCore.setValidatorAtMiningIndex(1, candidates[1]); + let miningValidator = await validatorsCore.getValidatorAtMiningIndex(1); + await expect(miningValidator.consensusAddr).eq(candidates[1].consensusAddr); }); - it('Should not be able to retrieve validator at 1-index while having 1 validator in the list', async () => { - await expect(validatorsCore.getValidatorAtMiningIndex(1)).to.revertedWith( - 'No validator exists at queried mining index' + it('Should not be able to retrieve validator at 2-index slot while having 1 validator in the list', async () => { + await expect(validatorsCore.getValidatorAtMiningIndex(2)).to.revertedWith( + 'Validator: No validator exists at queried mining index' ); }); - it('Should be able to add a new validator at 1-slot while having 1 validator in the list', async () => { - let miningValidator = await validatorsCore.getValidatorAtMiningIndex(0); - await expect(miningValidator.consensusAddr).eq(candidate1.consensusAddr); - - await validatorsCore.setValidatorAtMiningIndex(1, candidate2); - - miningValidator = await validatorsCore.getValidatorAtMiningIndex(1); - await expect(miningValidator.consensusAddr).eq(candidate2.consensusAddr); + it('Should be able to add a new validator at 2-index slot while having 1 validator in the list (set new on actual set)', async () => { + let miningValidator = await validatorsCore.getValidatorAtMiningIndex(1); + await expect(miningValidator.consensusAddr).eq(candidates[1].consensusAddr); + await validatorsCore.setValidatorAtMiningIndex(2, candidates[2]); + miningValidator = await validatorsCore.getValidatorAtMiningIndex(2); + await expect(miningValidator.consensusAddr).eq(candidates[2].consensusAddr); }); - it('Should not be able to add a new validator at 3-slot while having 2 validator in the list', async () => { - await expect(validatorsCore.setValidatorAtMiningIndex(3, candidate3)).to.revertedWith( - 'Cannot set mining index greater than current indexes array length' + it('Should not be able to add a new validator at 4-index slot while having 2 validator in the list', async () => { + await expect(validatorsCore.setValidatorAtMiningIndex(4, candidates[3])).to.revertedWith( + 'Validator: Cannot set at out-of-bound mining set' ); }); it('Should be able to swap the validator', async () => { - await validatorsCore.setValidator(candidate6, true); - await validatorsCore.setValidatorAtMiningIndex(1, candidate6); - - let miningValidator = await validatorsCore.getValidatorAtMiningIndex(1); - await expect(miningValidator.consensusAddr).eq(candidate6.consensusAddr); - - await expect(validatorsCore.getValidatorAtMiningIndex(2)).to.revertedWith( - 'No validator exists at queried mining index' + await validatorsCore.setValidator(candidates[6], true); + await validatorsCore.setValidatorAtMiningIndex(2, candidates[6]); + let miningValidator = await validatorsCore.getValidatorAtMiningIndex(2); + await expect(miningValidator.consensusAddr).eq(candidates[6].consensusAddr); + await expect(validatorsCore.getValidatorAtMiningIndex(3)).to.revertedWith( + 'Validator: No validator exists at queried mining index' ); }); it('Should be able to remove the validator', async () => { - await validatorsCore.removeValidatorAtMiningIndex(1); + expect(await validatorsCore.getCurrentValidatorSetSize()).to.eq(2); + await validatorsCore.popValidatorFromMiningIndex(); + expect(await validatorsCore.getCurrentValidatorSetSize()).to.eq(1); + }); + }); + + describe('Stress test', async () => { + before(async () => { + [admin, ...signers] = await ethers.getSigners(); + candidates = []; + validatorsCore = await new MockValidatorSetCore__factory(admin).deploy(); + + for (let i = 0; i < 33; i++) { + candidates.push(generateCandidate(signers[3*i].address, signers[3*i+1].address, signers[3*i+2].address)); + } + }); + + it('Should be able to add 10 validators', async () => { + for (let i = 1; i <= 10; i++) { + await validatorsCore.setValidator(candidates[i], false); + let _validator = await validatorsCore.getValidator(candidates[i].consensusAddr); + expect(_validator.consensusAddr).eq(candidates[i].consensusAddr); + } + }); + + it('Should be able to set in the indexes 10 new validators', async () => { + for (let i = 1; i <= 10; i++) { + await validatorsCore.setValidatorAtMiningIndex(i, candidates[i]); + let _validator = await validatorsCore.getValidatorAtMiningIndex(i); + await expect(_validator.consensusAddr).eq(candidates[i].consensusAddr); + } + }); + + it('Should be able to swap 10 existed validators', async () => { + let swapTable = [...Array(10).keys()] + .map(x => ++x) + .sort(() => .5 - Math.random()); + + console.log(swapTable) - await expect(validatorsCore.getValidatorAtMiningIndex(1)).to.revertedWith( - 'No validator exists at queried mining index' - ); + for (let i = 1; i <= 10; i++) { + console.log(">>> Swap index", i, "to", swapTable[i-1]); + await validatorsCore.setValidatorAtMiningIndex(i, candidates[swapTable[i-1]]); + let _validator = await validatorsCore.getValidatorAtMiningIndex(i); + expect(_validator.consensusAddr).eq(candidates[swapTable[i-1]].consensusAddr); + } + }); + + it('Should be able to add 20 validators more', async () => { + for (let i = 11; i <= 30; i++) { + await validatorsCore.setValidator(candidates[i], false); + let _validator = await validatorsCore.getValidator(candidates[i].consensusAddr); + expect(_validator.consensusAddr).eq(candidates[i].consensusAddr); + } + }); + + it('Should be able to set in the indexes for 11 new validators', async () => { + for (let i = 11; i <= 21; i++) { + await validatorsCore.setValidatorAtMiningIndex(i, candidates[i]); + let _validator = await validatorsCore.getValidatorAtMiningIndex(i); + await expect(_validator.consensusAddr).eq(candidates[i].consensusAddr); + } + }); + + it('Should be able to swap 21 existed validators', async () => { + let swapTable = [...Array(21).keys()] + .map(x => ++x) + .sort(() => .5 - Math.random()); + + console.log(swapTable) + + for (let i = 1; i <= 21; i++) { + console.log(">>> Swap index", i, "to", swapTable[i-1]); + await validatorsCore.setValidatorAtMiningIndex(i, candidates[swapTable[i-1]]); + let _validator = await validatorsCore.getValidatorAtMiningIndex(i); + expect(_validator.consensusAddr).eq(candidates[swapTable[i-1]].consensusAddr); + } }); }); }); From d675321bb1c205e290fce15f126bc9d7736a71b3 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Wed, 31 Aug 2022 10:52:37 +0700 Subject: [PATCH 032/190] chore: lint --- tests/validators/ValidatorSetCore.test.ts | 62 +++++++++++------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/tests/validators/ValidatorSetCore.test.ts b/tests/validators/ValidatorSetCore.test.ts index 7d90319b3..343b45b87 100644 --- a/tests/validators/ValidatorSetCore.test.ts +++ b/tests/validators/ValidatorSetCore.test.ts @@ -2,12 +2,8 @@ import { expect } from 'chai'; import { ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { - MockValidatorSetCore, - MockValidatorSetCore__factory, -} from '../../src/types'; +import { MockValidatorSetCore, MockValidatorSetCore__factory } from '../../src/types'; import { ValidatorCandidateStruct } from '../../src/types/ValidatorSetCoreMock'; -import { randomSigners } from '../../src/utils'; let validatorsCore: MockValidatorSetCore; @@ -18,7 +14,11 @@ let stakingAddrs: SignerWithAddress[]; let consensusAddrs: SignerWithAddress[]; let treasuryAddrs: SignerWithAddress[]; -const generateCandidate = (stakingAddr: string, consensusAddr: string, treasuryAddr: string): ValidatorCandidateStruct => { +const generateCandidate = ( + stakingAddr: string, + consensusAddr: string, + treasuryAddr: string +): ValidatorCandidateStruct => { return { stakingAddr: stakingAddr, consensusAddr: consensusAddr, @@ -37,17 +37,19 @@ describe('Validator set core tests', () => { before(async () => { [admin, ...signers] = await ethers.getSigners(); validatorsCore = await new MockValidatorSetCore__factory(admin).deploy(); - + candidates = []; stakingAddrs = []; consensusAddrs = []; treasuryAddrs = []; for (let i = 0; i < 7; i++) { - candidates.push(generateCandidate(signers[3*i].address, signers[3*i+1].address, signers[3*i+2].address)); - stakingAddrs.push(signers[3*i]); - consensusAddrs.push(signers[3*i + 1]); - treasuryAddrs.push(signers[3*i + 2]); + candidates.push( + generateCandidate(signers[3 * i].address, signers[3 * i + 1].address, signers[3 * i + 2].address) + ); + stakingAddrs.push(signers[3 * i]); + consensusAddrs.push(signers[3 * i + 1]); + treasuryAddrs.push(signers[3 * i + 2]); } }); @@ -70,7 +72,7 @@ describe('Validator set core tests', () => { await validatorsCore.setValidator(candidates[1], true); let validator = await validatorsCore.getValidator(consensusAddrs[1].address); expect(candidates[1].consensusAddr).eq(validator.consensusAddr); - expect(treasuryAddrs[5].address).eq(validator.treasuryAddr); + expect(treasuryAddrs[5].address).eq(validator.treasuryAddr); }); it('Should fail to query in mining validator set', async () => { @@ -128,7 +130,9 @@ describe('Validator set core tests', () => { validatorsCore = await new MockValidatorSetCore__factory(admin).deploy(); for (let i = 0; i < 33; i++) { - candidates.push(generateCandidate(signers[3*i].address, signers[3*i+1].address, signers[3*i+2].address)); + candidates.push( + generateCandidate(signers[3 * i].address, signers[3 * i + 1].address, signers[3 * i + 2].address) + ); } }); @@ -149,17 +153,15 @@ describe('Validator set core tests', () => { }); it('Should be able to swap 10 existed validators', async () => { - let swapTable = [...Array(10).keys()] - .map(x => ++x) - .sort(() => .5 - Math.random()); - - console.log(swapTable) - + let swapTable = [...Array(10).keys()].map((x) => ++x).sort(() => 0.5 - Math.random()); + + console.log(swapTable); + for (let i = 1; i <= 10; i++) { - console.log(">>> Swap index", i, "to", swapTable[i-1]); - await validatorsCore.setValidatorAtMiningIndex(i, candidates[swapTable[i-1]]); + console.log('>>> Swap index', i, 'to', swapTable[i - 1]); + await validatorsCore.setValidatorAtMiningIndex(i, candidates[swapTable[i - 1]]); let _validator = await validatorsCore.getValidatorAtMiningIndex(i); - expect(_validator.consensusAddr).eq(candidates[swapTable[i-1]].consensusAddr); + expect(_validator.consensusAddr).eq(candidates[swapTable[i - 1]].consensusAddr); } }); @@ -180,17 +182,15 @@ describe('Validator set core tests', () => { }); it('Should be able to swap 21 existed validators', async () => { - let swapTable = [...Array(21).keys()] - .map(x => ++x) - .sort(() => .5 - Math.random()); - - console.log(swapTable) - + let swapTable = [...Array(21).keys()].map((x) => ++x).sort(() => 0.5 - Math.random()); + + console.log(swapTable); + for (let i = 1; i <= 21; i++) { - console.log(">>> Swap index", i, "to", swapTable[i-1]); - await validatorsCore.setValidatorAtMiningIndex(i, candidates[swapTable[i-1]]); + console.log('>>> Swap index', i, 'to', swapTable[i - 1]); + await validatorsCore.setValidatorAtMiningIndex(i, candidates[swapTable[i - 1]]); let _validator = await validatorsCore.getValidatorAtMiningIndex(i); - expect(_validator.consensusAddr).eq(candidates[swapTable[i-1]].consensusAddr); + expect(_validator.consensusAddr).eq(candidates[swapTable[i - 1]].consensusAddr); } }); }); From 7ad79fe1b56800353d1900f6044b3f44a99e27e5 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Wed, 31 Aug 2022 10:53:31 +0700 Subject: [PATCH 033/190] [Misc] Add benchmark test for swapping on storage --- contracts/mocks/MockSwapStorage.sol | 38 +++++++++++ tests/sorting/MockSwapStorage.test.ts | 90 +++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 contracts/mocks/MockSwapStorage.sol create mode 100644 tests/sorting/MockSwapStorage.test.ts diff --git a/contracts/mocks/MockSwapStorage.sol b/contracts/mocks/MockSwapStorage.sol new file mode 100644 index 000000000..e9970e394 --- /dev/null +++ b/contracts/mocks/MockSwapStorage.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../interfaces/IValidatorSet.sol"; +import "../interfaces/IStaking.sol"; + +contract MockSwapStorage { + /// @dev Array of all validators. + IStaking.ValidatorCandidate[] public validatorSet; + + function pushValidator(IStaking.ValidatorCandidate memory _incomingValidator) + external + { + validatorSet.push(_incomingValidator); + } + + function _setValidatorAt(uint256 _index, IStaking.ValidatorCandidate memory _incomingValidator) public { + require(_index < validatorSet.length, "Access out-of-bound"); + + IStaking.ValidatorCandidate storage _validator = validatorSet[_index]; + _validator.stakingAddr = _incomingValidator.stakingAddr; + _validator.consensusAddr = _incomingValidator.consensusAddr; + _validator.treasuryAddr = _incomingValidator.treasuryAddr; + _validator.commissionRate = _incomingValidator.commissionRate; + _validator.stakedAmount = _incomingValidator.stakedAmount; + _validator.delegatedAmount = _incomingValidator.delegatedAmount; + _validator.governing = _incomingValidator.governing; + } + + function swapValidators(uint256 _i, uint256 _j) external { + require(_i < validatorSet.length, "Access left element out-of-bound"); + require(_j < validatorSet.length, "Access right element out-of-bound"); + IStaking.ValidatorCandidate memory _tmp = validatorSet[_i]; + _setValidatorAt(_i, validatorSet[_j]); + _setValidatorAt(_j, _tmp); + } +} diff --git a/tests/sorting/MockSwapStorage.test.ts b/tests/sorting/MockSwapStorage.test.ts new file mode 100644 index 000000000..141f652f2 --- /dev/null +++ b/tests/sorting/MockSwapStorage.test.ts @@ -0,0 +1,90 @@ +import { expect } from 'chai'; +import { ethers } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; + +import { MockSwapStorage, MockSwapStorage__factory } from '../../src/types'; +import { ValidatorCandidateStruct } from '../../src/types/ValidatorSetCoreMock'; + +let swapping: MockSwapStorage; + +let signers: SignerWithAddress[]; +let candidates: ValidatorCandidateStruct[]; +let admin: SignerWithAddress; + +const generateCandidate = ( + stakingAddr: string, + consensusAddr: string, + treasuryAddr: string +): ValidatorCandidateStruct => { + return { + stakingAddr: stakingAddr, + consensusAddr: consensusAddr, + treasuryAddr: treasuryAddr, + commissionRate: 0, + stakedAmount: 0, + delegatedAmount: 0, + governing: false, + ____gap: Array.apply(null, Array(20)).map((_) => 0), + }; +}; + +describe('Mock swap struct on storage tests', () => { + describe('Stress test', async () => { + before(async () => { + [admin, ...signers] = await ethers.getSigners(); + candidates = []; + swapping = await new MockSwapStorage__factory(admin).deploy(); + + for (let i = 0; i < 33; i++) { + candidates.push( + generateCandidate(signers[3 * i].address, signers[3 * i + 1].address, signers[3 * i + 2].address) + ); + } + }); + + it('Should be able to add 2 validators', async () => { + for (let i = 0; i < 2; i++) { + await swapping.pushValidator(candidates[i]); + } + }); + + it('Should be able to swap 2 existed validators', async () => { + let swapTable = [1, 0]; + console.log(swapTable); + for (let i = 0; i < 2; i++) { + console.log('>>> Swap index', i, 'to', swapTable[i]); + await swapping.swapValidators(i, swapTable[i]); + } + }); + + it('Should be able to add 9 more validators', async () => { + for (let i = 2; i <= 10; i++) { + await swapping.pushValidator(candidates[i]); + } + }); + + it('Should be able to swap 11 existed validators', async () => { + let swapTable = [...Array(11).keys()].map((x) => x).sort(() => 0.5 - Math.random()); + console.log(swapTable); + for (let i = 0; i <= 10; i++) { + console.log('>>> Swap index', i, 'to', swapTable[i]); + await swapping.swapValidators(i, swapTable[i]); + } + }); + + it('Should be able to add 20 validators more', async () => { + for (let i = 11; i <= 30; i++) { + await swapping.pushValidator(candidates[i]); + } + }); + + it('Should be able to swap 31 existed validators', async () => { + let swapTable = [...Array(31).keys()].map((x) => x).sort(() => 0.5 - Math.random()); + console.log(swapTable); + for (let i = 0; i <= 30; i++) { + console.log('>>> Swap index', i, 'to', swapTable[i]); + await swapping.swapValidators(i, swapTable[i]); + } + }); + }); +}); From 215c5d89f2abff2b376a3d6a768056958c801578 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Wed, 31 Aug 2022 12:05:42 +0700 Subject: [PATCH 034/190] [Staking] Add deploy script --- contracts/Staking.sol | 22 +++++++++++++- contracts/mocks/MockValidatorSetCore.sol | 1 + hardhat.config.ts | 2 +- src/deploy/libraries/sorting.ts | 16 ++++++++++ src/deploy/proxy-admin.ts | 15 ++++++++++ src/deploy/staking.ts | 38 ++++++++++++++++++++++++ 6 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 src/deploy/libraries/sorting.ts create mode 100644 src/deploy/proxy-admin.ts create mode 100644 src/deploy/staking.ts diff --git a/contracts/Staking.sol b/contracts/Staking.sol index ed8c4c5b7..b4200f1ce 100644 --- a/contracts/Staking.sol +++ b/contracts/Staking.sol @@ -7,7 +7,7 @@ import "./interfaces/IStaking.sol"; import "./interfaces/IValidatorSet.sol"; import "./libraries/Sorting.sol"; -abstract contract Staking is IStaking, Initializable { +contract Staking is IStaking, Initializable { /// TODO: expose fn to get validator info by validator address. /// @dev Mapping from consensus address => validator index in `validatorCandidates`. mapping(address => uint256) internal _validatorIndexes; @@ -166,6 +166,26 @@ abstract contract Staking is IStaking, Initializable { revert("Unimplemented"); } + function onDeposit() external payable override { + revert("Unimplemented"); + } + + function allocateReward(address valAddr, uint256 amount) external override { + revert("Unimplemented"); + } + + function receiveReward(address valAddr) external payable override { + revert("Unimplemented"); + } + + function claimReward(uint256 amount) external override { + revert("Unimplemented"); + } + + function slash(address valAddr, uint256 amount) external override { + revert("Unimplemented"); + } + /** * @dev Delegates from a validator address. * diff --git a/contracts/mocks/MockValidatorSetCore.sol b/contracts/mocks/MockValidatorSetCore.sol index 268775449..330a6ef45 100644 --- a/contracts/mocks/MockValidatorSetCore.sol +++ b/contracts/mocks/MockValidatorSetCore.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.9; import "../ValidatorSetCore.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; /** * @title Set of validators in current epoch diff --git a/hardhat.config.ts b/hardhat.config.ts index 2d3d8ec39..492282839 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -57,7 +57,7 @@ const config: HardhatUserConfig = { }, namedAccounts: { deployer: 0, - trezor: 'trezor://0x0000000000000000000000000000000000000000', + // trezor: 'trezor://0x0000000000000000000000000000000000000000', }, networks: { hardhat: { diff --git a/src/deploy/libraries/sorting.ts b/src/deploy/libraries/sorting.ts new file mode 100644 index 000000000..e01f6f602 --- /dev/null +++ b/src/deploy/libraries/sorting.ts @@ -0,0 +1,16 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + await deploy('SortingLibrary', { + contract: 'Sorting', + from: deployer, + log: true, + }); +}; + +deploy.tags = ['SortingLibrary']; + +export default deploy; diff --git a/src/deploy/proxy-admin.ts b/src/deploy/proxy-admin.ts new file mode 100644 index 000000000..781cf2f9e --- /dev/null +++ b/src/deploy/proxy-admin.ts @@ -0,0 +1,15 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + await deploy('ProxyAdmin', { + from: deployer, + log: true, + }); +}; + +deploy.tags = ['ProxyAdmin']; + +export default deploy; diff --git a/src/deploy/staking.ts b/src/deploy/staking.ts new file mode 100644 index 000000000..6a1a17f34 --- /dev/null +++ b/src/deploy/staking.ts @@ -0,0 +1,38 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +import { Staking__factory } from '../types'; +import { StakingLibraryAddresses } from '../types/factories/Staking__factory'; + +const deploy = async ({ getNamedAccounts, deployments}: HardhatRuntimeEnvironment) => { + const { deploy } = deployments; + let { deployer } = await getNamedAccounts(); + + const proxyAdmin = await deployments.get('ProxyAdmin'); + const sortingLibrary = await deployments.get('SortingLibrary'); + const stakingLogic = await deploy('StakingLogic', { + contract: 'Staking', + from: deployer, + log: true, + libraries: { + Sorting: sortingLibrary.address, + }, + }); + + const param: StakingLibraryAddresses = { + ['contracts/libraries/Sorting.sol:Sorting']: sortingLibrary.address, + }; + + const data = new Staking__factory(param).interface.encodeFunctionData('initialize', []); + + await deploy('StakingProxy', { + contract: 'TransparentUpgradeableProxy', + from: deployer, + log: true, + args: [stakingLogic.address, proxyAdmin.address, data], + }); +}; + +deploy.tags = ['StakingContract']; +deploy.dependencies = ['ProxyAdmin', 'SortingLibrary']; + +export default deploy; From 90352a0ffaf0a2d520a78b7b25d008d995e65957 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 31 Aug 2022 12:52:05 +0700 Subject: [PATCH 035/190] [CoreStaking] update contract & test --- contracts/mocks/MockManager.sol | 62 ++--- contracts/staking/CoreStaking.sol | 187 +++++++++---- test/staking/CoreStaking.test.ts | 423 ++++++++++++++++-------------- 3 files changed, 392 insertions(+), 280 deletions(-) diff --git a/contracts/mocks/MockManager.sol b/contracts/mocks/MockManager.sol index fcffb9afb..706f8501e 100644 --- a/contracts/mocks/MockManager.sol +++ b/contracts/mocks/MockManager.sol @@ -15,8 +15,8 @@ contract MockManager is CoreStaking { uint256 public totalBalance; - StakingPool public pool; - StakingPoolSnapshot public poolSnapshot; + PendingPool public pool; + SettledPool public sPool; constructor() { _epoches.push(0); @@ -29,7 +29,7 @@ contract MockManager is CoreStaking { function stake(address _user, uint256 _amount) external { uint256 _balance = _stakingBalance[_user]; uint256 _newBalance = _balance + _amount; - _syncUserInfo(_user, _newBalance); + _syncUserReward(_user, _newBalance); _stakingBalance[_user] = _newBalance; totalBalance += _amount; } @@ -37,7 +37,7 @@ contract MockManager is CoreStaking { function unstake(address _user, uint256 _amount) external { uint256 _balance = _stakingBalance[_user]; uint256 _newBalance = _balance - _amount; - _syncUserInfo(_user, _newBalance); + _syncUserReward(_user, _newBalance); _stakingBalance[_user] = _newBalance; totalBalance -= _amount; } @@ -47,7 +47,7 @@ contract MockManager is CoreStaking { } function commitRewardPool() public { - StakingPoolSnapshot storage _sPool = poolSnapshot; + SettledPool storage _sPool = sPool; _sPool.accumulatedRps = pool.accumulatedRps; _sPool.lastSyncBlock = block.number; } @@ -59,15 +59,15 @@ contract MockManager is CoreStaking { function increaseAndSyncSnapshot(uint256 _amount) external { increaseAccumulatedRps(_amount); - poolSnapshot.accumulatedRps = pool.accumulatedRps; - poolSnapshot.lastSyncBlock = block.number; + sPool.accumulatedRps = pool.accumulatedRps; + sPool.lastSyncBlock = block.number; } function slash() external { uint256 _epoch = getEpoch(); console.log("Slash block=", block.number, "at epoch=", _epoch); _epochSlashed[_epoch] = true; - pool.accumulatedRps = poolSnapshot.accumulatedRps; + pool.accumulatedRps = sPool.accumulatedRps; pool.lastSyncBlock = block.number; } @@ -76,44 +76,26 @@ contract MockManager is CoreStaking { } function claimReward(address _user) public returns (uint256 _amount) { - uint256 _balance = getCurrentBalance(_user); + uint256 _balance = balanceOf(_user); _amount = getClaimableReward(_user); - StakingPoolSnapshot memory _sPool = _getStakingPoolSnapshot(); + SettledPool memory _sPool = _getSettledPool(); + // PendingPool memory _pool = _getStakingPool(); console.log("User", _user, "claimed", _amount); - UserRewardInfo storage _reward = _getUserRewardInfo(_user); - console.log("claimReward: \t =>", _reward.debited); - _reward.debited = 0; - _reward.credited = (_balance * _sPool.accumulatedRps) / 1e18; + PendingRewardFields storage _reward = _getPendingRewardFields(_user); + console.log("claimReward: \t => (+)=", _reward.debited, _reward.debited); + console.log("claimReward: \t => (-)=", _reward.credited, _reward.credited + _amount); + // _reward.debited = 0; + _reward.credited += _amount; _reward.lastSyncBlock = block.number; console.log("claimReward: \t", _balance, _sPool.accumulatedRps); - UserRewardInfoSnapshot storage _sReward = _getUserRewardInfoSnapshot(_user); + SettledRewardFields storage _sReward = _getSettledRewardFields(_user); _sReward.debited = 0; _sReward.accumulatedRps = _sPool.accumulatedRps; } - function getClaimableReward(address _user) public view returns (uint256) { - UserRewardInfo memory _reward = _getUserRewardInfo(_user); - UserRewardInfoSnapshot memory _sReward = _getUserRewardInfoSnapshot(_user); - StakingPoolSnapshot memory _sPool = _getStakingPoolSnapshot(); - - console.log("-> getClaimableReward", _reward.lastSyncBlock, _sPool.lastSyncBlock); - if (_reward.lastSyncBlock <= _sPool.lastSyncBlock) { - console.log("\t-> sync"); - _sReward.debited = (getCurrentBalance(_user) * _sPool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; - _sReward.balance = getCurrentBalance(_user); - _sReward.accumulatedRps = _sPool.accumulatedRps; - } - - uint256 _balance = _sReward.balance; - uint256 _credited = (_balance * _sReward.accumulatedRps) / 1e18; - console.log("\t", _balance, _sReward.accumulatedRps, _sPool.accumulatedRps); - console.log("\t", _sReward.debited); - return (_balance * _sPool.accumulatedRps) / 1e18 + _sReward.debited - _credited; - } - - function _slashed(StakingPool memory, uint256 _epoch) internal view override returns (bool) { + function _slashed(PendingPool memory, uint256 _epoch) internal view override returns (bool) { return _epochSlashed[_epoch]; } @@ -125,15 +107,15 @@ contract MockManager is CoreStaking { } } - function _getStakingPool() internal view override returns (StakingPool memory) { + function _getPendingPool() internal view override returns (PendingPool memory) { return pool; } - function _getStakingPoolSnapshot() internal view override returns (StakingPoolSnapshot memory) { - return poolSnapshot; + function _getSettledPool() internal view override returns (SettledPool memory) { + return sPool; } - function getCurrentBalance(address _user) public view override returns (uint256) { + function balanceOf(address _user) public view override returns (uint256) { return _stakingBalance[_user]; } } diff --git a/contracts/staking/CoreStaking.sol b/contracts/staking/CoreStaking.sol index 02a88b09e..248c21260 100644 --- a/contracts/staking/CoreStaking.sol +++ b/contracts/staking/CoreStaking.sol @@ -2,96 +2,183 @@ pragma solidity ^0.8.9; -import "hardhat/console.sol"; +// import "hardhat/console.sol"; abstract contract CoreStaking { - struct UserRewardInfo { - uint256 debited; // + - uint256 credited; // - + struct PendingRewardFields { + // Recorded reward amount. + uint256 debited; + // The amount rewards that user have already earned. + uint256 credited; + // Last block number that the info updated. uint256 lastSyncBlock; } - struct UserRewardInfoSnapshot { + struct SettledRewardFields { + // The balance at the commit time. uint256 balance; - uint256 debited; // + - /// @dev Accumulated of the amount rewards per share (one unit staking). + // Recorded reward amount. + uint256 debited; + // Accumulated of the amount rewards per share (one unit staking). uint256 accumulatedRps; } - struct StakingPool { + struct PendingPool { + // Last block number that the info updated. uint256 lastSyncBlock; - /// @dev Accumulated of the amount rewards per share (one unit staking). + // Accumulated of the amount rewards per share (one unit staking). uint256 accumulatedRps; } - struct StakingPoolSnapshot { + struct SettledPool { + // Last block number that the info updated. uint256 lastSyncBlock; - /// @dev Accumulated of the amount rewards per share (one unit staking). + // Accumulated of the amount rewards per share (one unit staking). uint256 accumulatedRps; } - mapping(address => UserRewardInfoSnapshot) internal _sUserReward; - mapping(address => UserRewardInfo) internal _userReward; - - function _syncUserInfo(address _user, uint256 _newBalance) internal { - UserRewardInfo storage _reward = _getUserRewardInfo(_user); - StakingPoolSnapshot memory _sPool = _getStakingPoolSnapshot(); - - uint256 _lastUserReward = getUserReward(_user); - // Syncs the user reward snapshot once the last sync is committed (snapshotted). + /// @dev Mapping from the user address => settled reward info of the user + mapping(address => SettledRewardFields) internal _sUserReward; + /// @dev Mapping from the user address => pending reward info of the user + mapping(address => PendingRewardFields) internal _pUserReward; + + /** + * @dev Syncs the user reward. + * + * Emits the `` event and the `` event. + * + * @notice The method should be called whenever the user's balance changes. + * + * TODO: add test to stake/unstake many times in block. + * TODO: add events. + * + */ + function _syncUserReward(address _user, uint256 _newBalance) internal { + PendingRewardFields storage _reward = _getPendingRewardFields(_user); + SettledPool memory _sPool = _getSettledPool(); + + // Syncs the reward once the last sync is settled. if (_reward.lastSyncBlock <= _sPool.lastSyncBlock) { - UserRewardInfoSnapshot storage _sReward = _getUserRewardInfoSnapshot(_user); - console.log("Synced for", _user, _lastUserReward, _sPool.accumulatedRps); - _sReward.balance = getCurrentBalance(_user); - _sReward.debited = _lastUserReward; + SettledRewardFields storage _sReward = _getSettledRewardFields(_user); + uint256 _claimableReward = getClaimableReward(_user); + // console.log("Synced for", _user, _claimableReward, _sPool.accumulatedRps); + _sReward.balance = balanceOf(_user); + _sReward.debited = _claimableReward; _sReward.accumulatedRps = _sPool.accumulatedRps; } - StakingPool memory _pool = _getStakingPool(); - _reward.debited = _lastUserReward; + PendingPool memory _pool = _getPendingPool(); + _reward.debited = getTotalReward(_user); _reward.credited = (_newBalance * _pool.accumulatedRps) / 1e18; _reward.lastSyncBlock = block.number; } - function getUserReward(address _user) public view returns (uint256) { - UserRewardInfo memory _reward = _getUserRewardInfo(_user); - StakingPool memory _pool = _getStakingPool(); + /** + * @dev Returns the total reward. + */ + function getTotalReward(address _user) public view returns (uint256) { + PendingRewardFields memory _reward = _getPendingRewardFields(_user); + PendingPool memory _pool = _getPendingPool(); - uint256 _balance = getCurrentBalance(_user); + uint256 _balance = balanceOf(_user); if (_slashed(_pool, _blockNumberToEpoch(_reward.lastSyncBlock))) { - UserRewardInfoSnapshot memory _sReward = _getUserRewardInfoSnapshot(_user); - return _getUserRewardOnSlashed(_sReward, _pool.accumulatedRps, _balance); + SettledRewardFields memory _sReward = _getSettledRewardFields(_user); + uint256 _credited = (_sReward.accumulatedRps * _balance) / 1e18; + return (_balance * _pool.accumulatedRps) / 1e18 + _sReward.debited - _credited; } - console.log("getUserReward:\t", _balance, _pool.accumulatedRps, _reward.debited); - console.log("getUserReward:\t", _reward.credited); + // console.log("_getUserReward:\t", _balance, _pool.accumulatedRps, _reward.debited); + // console.log("_getUserReward:\t", _reward.credited); return (_balance * _pool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; } - function _getUserRewardOnSlashed( - UserRewardInfoSnapshot memory _sReward, - uint256 _accumulatedRps, - uint256 _balance - ) internal pure returns (uint256) { - uint256 _credited = (_sReward.accumulatedRps * _balance) / 1e18; - return (_balance * _accumulatedRps) / 1e18 + _sReward.debited - _credited; + function getClaimableReward(address _user) public view returns (uint256) { + PendingRewardFields memory _reward = _getPendingRewardFields(_user); + SettledRewardFields memory _sReward = _getSettledRewardFields(_user); + SettledPool memory _sPool = _getSettledPool(); + + // console.log("-> getClaimableReward", _user, _reward.lastSyncBlock, _sPool.lastSyncBlock); + if (_reward.lastSyncBlock <= _sPool.lastSyncBlock) { + // console.log("\t-> sync"); + uint256 _currentBalance = balanceOf(_user); + _sReward.debited = (_currentBalance * _sPool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; + _sReward.balance = _currentBalance; + _sReward.accumulatedRps = _sPool.accumulatedRps; + } + + uint256 _balance = _sReward.balance; + uint256 _credited = (_balance * _sReward.accumulatedRps) / 1e18; + // console.log("\t", _balance, _sReward.accumulatedRps, _sPool.accumulatedRps); + // console.log("\t", _sReward.debited); + return (_balance * _sPool.accumulatedRps) / 1e18 + _sReward.debited - _credited; } - function _getUserRewardInfo(address _user) internal view virtual returns (UserRewardInfo storage) { - return _userReward[_user]; + /** + * @dev Returns the pending reward. + */ + function getPendingReward(address _user) external view returns (uint256 _amount) { + _amount = getTotalReward(_user) - getClaimableReward(_user); } - function _getUserRewardInfoSnapshot(address _user) internal view virtual returns (UserRewardInfoSnapshot storage) { - return _sUserReward[_user]; + /** + * @dev Claims the settled reward for a specific user. + */ + function _claimReward(address _user) public returns (uint256 _amount) { + // uint256 _balance = balanceOf(_user); + _amount = getClaimableReward(_user); + SettledPool memory _sPool = _getSettledPool(); + // PendingPool memory _pool = _getStakingPool(); + // console.log("User", _user, "claimed", _amount); + + PendingRewardFields storage _reward = _getPendingRewardFields(_user); + // console.log("claimReward: \t => (+)=", _reward.debited, _reward.debited); + // console.log("claimReward: \t => (-)=", _reward.credited, _reward.credited + _amount); + // _reward.debited = 0; + _reward.credited += _amount; + _reward.lastSyncBlock = block.number; + // console.log("claimReward: \t", _balance, _sPool.accumulatedRps); + + SettledRewardFields storage _sReward = _getSettledRewardFields(_user); + _sReward.debited = 0; + _sReward.accumulatedRps = _sPool.accumulatedRps; } - function _slashed(StakingPool memory, uint256) internal view virtual returns (bool) {} + /** + * @dev Returns the pending pool. + */ + function _getPendingPool() internal view virtual returns (PendingPool memory) {} + + /** + * @dev Returns settled pool. + */ + function _getSettledPool() internal view virtual returns (SettledPool memory) {} + + /** + * @dev Returns the pending reward info of a specific user. + */ + function _getPendingRewardFields(address _user) internal view virtual returns (PendingRewardFields storage) { + return _pUserReward[_user]; + } - function _blockNumberToEpoch(uint256) internal view virtual returns (uint256) {} + /** + * @dev Returns the settled reward info of a specific user. + */ + function _getSettledRewardFields(address _user) internal view virtual returns (SettledRewardFields storage) { + return _sUserReward[_user]; + } - function _getStakingPool() internal view virtual returns (StakingPool memory) {} + /** + * @dev Returns whether the pool is slashed in the epoch `_epoch`. + */ + function _slashed(PendingPool memory, uint256 _epoch) internal view virtual returns (bool); - function _getStakingPoolSnapshot() internal view virtual returns (StakingPoolSnapshot memory) {} + /** + * @dev Returns the epoch from the block number. + */ + function _blockNumberToEpoch(uint256) internal view virtual returns (uint256); - function getCurrentBalance(address) public view virtual returns (uint256) {} + /** + * @dev Returns the staking amount of the user. + */ + function balanceOf(address) public view virtual returns (uint256); } diff --git a/test/staking/CoreStaking.test.ts b/test/staking/CoreStaking.test.ts index 67d4fde9d..3ec0fa055 100644 --- a/test/staking/CoreStaking.test.ts +++ b/test/staking/CoreStaking.test.ts @@ -19,8 +19,8 @@ const local = { claimableRewardForB: BigNumber.from(0), recordReward: async function (reward: BigNumberish) { const totalStaked = await manager.totalBalance(); - const stakingAmountA = await manager.getCurrentBalance(userA.address); - const stakingAmountB = await manager.getCurrentBalance(userB.address); + const stakingAmountA = await manager.balanceOf(userA.address); + const stakingAmountB = await manager.balanceOf(userB.address); this.accumulatedRewardForA = this.accumulatedRewardForA.add( BigNumber.from(reward).mul(stakingAmountA).div(totalStaked) ); @@ -54,7 +54,7 @@ const local = { const expectLocalCalculationRight = async () => { { - const userReward = await manager.getUserReward(userA.address); + const userReward = await manager.getTotalReward(userA.address); expect( userReward.sub(local.accumulatedRewardForA).abs().lte(EPS), `invalid user reward for A expected=${local.accumulatedRewardForA.toString()} actual=${userReward}` @@ -66,7 +66,7 @@ const expectLocalCalculationRight = async () => { ).to.be.true; } { - const userReward = await manager.getUserReward(userB.address); + const userReward = await manager.getTotalReward(userB.address); expect( userReward.sub(local.accumulatedRewardForB).abs().lte(EPS), `invalid user reward for B expected=${local.accumulatedRewardForB.toString()} actual=${userReward}` @@ -80,191 +80,234 @@ const expectLocalCalculationRight = async () => { }; describe('Core Staking test', () => { - describe('Total reward calculation', async () => { - before(async () => { - [deployer, userA, userB] = await ethers.getSigners(); - manager = await new MockManager__factory(deployer).deploy(); - await network.provider.send('evm_setAutomine', [false]); - local.reset(); - }); - - it('Should be able to stake/unstake and get right total reward for a successful epoch', async () => { - await manager.stake(userA.address, 100); - await manager.stake(userB.address, 100); - await network.provider.send('evm_mine'); - - await manager.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await manager.recordReward(1000); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await manager.stake(userA.address, 200); - await network.provider.send('evm_mine'); - await expectLocalCalculationRight(); - - await manager.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await manager.unstake(userA.address, 200); - await manager.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await manager.stake(userA.address, 200); - await network.provider.send('evm_mine'); - await local.recordReward(0); - await expectLocalCalculationRight(); - - await manager.commitRewardPool(); - await manager.endEpoch(); - await network.provider.send('evm_mine'); - local.commitRewardPool(); - await expectLocalCalculationRight(); - }); - - it('Should be able to stake/unstake and get right total reward for a slashed epoch', async () => { - await manager.stake(userA.address, 100); - await network.provider.send('evm_mine'); - await expectLocalCalculationRight(); - await local.recordReward(0); - - await manager.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await manager.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await manager.stake(userA.address, 300); - await manager.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await manager.slash(); - await network.provider.send('evm_mine'); - local.slash(); - await expectLocalCalculationRight(); - - await manager.recordReward(0); - await network.provider.send('evm_mine'); - await local.recordReward(0); - await expectLocalCalculationRight(); - - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - - await manager.unstake(userA.address, 300); - await network.provider.send('evm_mine'); - await manager.unstake(userA.address, 100); - await network.provider.send('evm_mine'); - await expectLocalCalculationRight(); - - await manager.commitRewardPool(); - await manager.endEpoch(); - await network.provider.send('evm_mine'); - await expectLocalCalculationRight(); - }); - - it('Should be able to stake/unstake/claim and get right total reward for a slashed epoch again', async () => { - await manager.stake(userA.address, 100); - await network.provider.send('evm_mine'); - await local.recordReward(0); - await expectLocalCalculationRight(); - - await manager.claimReward(userA.address); - await manager.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - local.claimRewardForA(); - await expectLocalCalculationRight(); - - await manager.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await manager.stake(userA.address, 300); - await manager.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await manager.slash(); - await network.provider.send('evm_mine'); - local.slash(); - await expectLocalCalculationRight(); - - await manager.recordReward(0); - await network.provider.send('evm_mine'); - await local.recordReward(0); - await expectLocalCalculationRight(); - - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - - await manager.unstake(userA.address, 300); - await network.provider.send('evm_mine'); - await manager.unstake(userA.address, 100); - await network.provider.send('evm_mine'); - await expectLocalCalculationRight(); - - await manager.claimReward(userB.address); - await manager.commitRewardPool(); - await manager.endEpoch(); - await network.provider.send('evm_mine'); - local.claimRewardForB(); - await expectLocalCalculationRight(); - }); - - it('Should be able to get right claimable reward for the committed epoch', async () => { - await manager.recordReward(1000); - await manager.commitRewardPool(); - await manager.endEpoch(); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - local.commitRewardPool(); - await expectLocalCalculationRight(); - console.log(local); - console.log('================='); - - await manager.claimReward(userA.address); - await manager.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - local.claimRewardForA(); - console.log(local, await ethers.provider.getBlockNumber()); - await expectLocalCalculationRight(); - console.log('================='); - - await manager.claimReward(userA.address); - await network.provider.send('evm_mine'); - local.claimRewardForA(); - console.log(local, await ethers.provider.getBlockNumber()); - await expectLocalCalculationRight(); - console.log('================='); - - await manager.claimReward(userB.address); - await network.provider.send('evm_mine'); - local.claimRewardForB(); - await expectLocalCalculationRight(); - }); + before(async () => { + [deployer, userA, userB] = await ethers.getSigners(); + manager = await new MockManager__factory(deployer).deploy(); + await network.provider.send('evm_setAutomine', [false]); + local.reset(); + }); + + it('Should be able to stake/unstake and get right total reward for a successful epoch', async () => { + await manager.stake(userA.address, 100); + await manager.stake(userB.address, 100); + await network.provider.send('evm_mine'); + + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.stake(userA.address, 200); + await network.provider.send('evm_mine'); + await expectLocalCalculationRight(); + + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.unstake(userA.address, 200); + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.stake(userA.address, 200); + await network.provider.send('evm_mine'); + await local.recordReward(0); + await expectLocalCalculationRight(); + + await manager.commitRewardPool(); + await manager.endEpoch(); + await network.provider.send('evm_mine'); + local.commitRewardPool(); + await expectLocalCalculationRight(); + }); + + it('Should be able to stake/unstake and get right total reward for a slashed epoch', async () => { + await manager.stake(userA.address, 100); + await network.provider.send('evm_mine'); + await expectLocalCalculationRight(); + await local.recordReward(0); + + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.stake(userA.address, 300); + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.slash(); + await network.provider.send('evm_mine'); + local.slash(); + await expectLocalCalculationRight(); + + await manager.recordReward(0); + await network.provider.send('evm_mine'); + await local.recordReward(0); + await expectLocalCalculationRight(); + + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + + await manager.unstake(userA.address, 300); + await network.provider.send('evm_mine'); + await manager.unstake(userA.address, 100); + await network.provider.send('evm_mine'); + await expectLocalCalculationRight(); + + await manager.commitRewardPool(); + await manager.endEpoch(); + await network.provider.send('evm_mine'); + await expectLocalCalculationRight(); + }); + + it('Should be able to stake/unstake/claim and get right total reward for a slashed epoch again', async () => { + await manager.stake(userA.address, 100); + await network.provider.send('evm_mine'); + await local.recordReward(0); + await expectLocalCalculationRight(); + + await manager.claimReward(userA.address); + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + local.claimRewardForA(); + await expectLocalCalculationRight(); + + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.stake(userA.address, 300); + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.slash(); + await network.provider.send('evm_mine'); + local.slash(); + await expectLocalCalculationRight(); + + await manager.recordReward(0); + await network.provider.send('evm_mine'); + await local.recordReward(0); + await expectLocalCalculationRight(); + + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + + await manager.unstake(userA.address, 300); + await network.provider.send('evm_mine'); + await manager.unstake(userA.address, 100); + await network.provider.send('evm_mine'); + await expectLocalCalculationRight(); + + await manager.claimReward(userB.address); + await manager.commitRewardPool(); + await manager.endEpoch(); + await network.provider.send('evm_mine'); + local.claimRewardForB(); + await expectLocalCalculationRight(); + }); + + it('Should be able to get right claimable reward for the committed epoch', async () => { + await manager.recordReward(1000); + await manager.commitRewardPool(); + await manager.endEpoch(); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + local.commitRewardPool(); + await expectLocalCalculationRight(); + + await manager.claimReward(userA.address); + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + local.claimRewardForA(); + await expectLocalCalculationRight(); + + await manager.claimReward(userA.address); + await network.provider.send('evm_mine'); + local.claimRewardForA(); + console.log(local, await ethers.provider.getBlockNumber()); + await expectLocalCalculationRight(); + + await manager.claimReward(userB.address); + await manager.commitRewardPool(); + await manager.endEpoch(); + await network.provider.send('evm_mine'); + local.claimRewardForB(); + local.commitRewardPool(); + await expectLocalCalculationRight(); + }); + + it('Should be able to get right claimable reward test', async () => { + console.log(0, local); + await manager.stake(userA.address, 100); + await network.provider.send('evm_mine'); + + console.log(1, local); + await manager.stake(userA.address, 300); + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + console.log(2, local); + await manager.stake(userB.address, 200); + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await manager.unstake(userB.address, 200); + await manager.unstake(userA.address, 400); + await manager.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + console.log(3, local); + await manager.claimReward(userA.address); + await manager.claimReward(userB.address); + await network.provider.send('evm_mine'); + local.claimRewardForA(); + local.claimRewardForB(); + await expectLocalCalculationRight(); + + console.log(4, local); }); }); From f7e5264a5d25539d39eaafcd7c4808ba0257a6bd Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Sat, 3 Sep 2022 12:12:59 +0700 Subject: [PATCH 036/190] [CoreStaking] Finalize & rename to RewardCalculation, add test --- contracts/mocks/MockManager.sol | 121 ------------ contracts/mocks/MockStaking.sol | 87 +++++++++ contracts/staking/CoreStaking.sol | 184 ------------------ contracts/staking/RewardCalculation.sol | 246 ++++++++++++++++++++++++ test/staking/CoreStaking.test.ts | 236 +++++++++++++++-------- 5 files changed, 492 insertions(+), 382 deletions(-) delete mode 100644 contracts/mocks/MockManager.sol create mode 100644 contracts/mocks/MockStaking.sol delete mode 100644 contracts/staking/CoreStaking.sol create mode 100644 contracts/staking/RewardCalculation.sol diff --git a/contracts/mocks/MockManager.sol b/contracts/mocks/MockManager.sol deleted file mode 100644 index 706f8501e..000000000 --- a/contracts/mocks/MockManager.sol +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -import "hardhat/console.sol"; -import "../staking/CoreStaking.sol"; - -contract MockManager is CoreStaking { - /// @dev Mapping from user => staking balance - mapping(address => uint256) internal _stakingBalance; - /// @dev Mapping from epoch number => slashed - mapping(uint256 => bool) internal _epochSlashed; - - uint256[] internal _epoches; - - uint256 public totalBalance; - - PendingPool public pool; - SettledPool public sPool; - - constructor() { - _epoches.push(0); - } - - function endEpoch() external { - _epoches.push(block.number); - } - - function stake(address _user, uint256 _amount) external { - uint256 _balance = _stakingBalance[_user]; - uint256 _newBalance = _balance + _amount; - _syncUserReward(_user, _newBalance); - _stakingBalance[_user] = _newBalance; - totalBalance += _amount; - } - - function unstake(address _user, uint256 _amount) external { - uint256 _balance = _stakingBalance[_user]; - uint256 _newBalance = _balance - _amount; - _syncUserReward(_user, _newBalance); - _stakingBalance[_user] = _newBalance; - totalBalance -= _amount; - } - - function recordReward(uint256 _rewardAmount) public { - increaseAccumulatedRps((_rewardAmount * 1e18) / totalBalance); - } - - function commitRewardPool() public { - SettledPool storage _sPool = sPool; - _sPool.accumulatedRps = pool.accumulatedRps; - _sPool.lastSyncBlock = block.number; - } - - function increaseAccumulatedRps(uint256 _amount) public { - pool.accumulatedRps += _amount; - pool.lastSyncBlock = block.number; - } - - function increaseAndSyncSnapshot(uint256 _amount) external { - increaseAccumulatedRps(_amount); - sPool.accumulatedRps = pool.accumulatedRps; - sPool.lastSyncBlock = block.number; - } - - function slash() external { - uint256 _epoch = getEpoch(); - console.log("Slash block=", block.number, "at epoch=", _epoch); - _epochSlashed[_epoch] = true; - pool.accumulatedRps = sPool.accumulatedRps; - pool.lastSyncBlock = block.number; - } - - function getEpoch() public view returns (uint256) { - return _blockNumberToEpoch(block.number); - } - - function claimReward(address _user) public returns (uint256 _amount) { - uint256 _balance = balanceOf(_user); - _amount = getClaimableReward(_user); - SettledPool memory _sPool = _getSettledPool(); - // PendingPool memory _pool = _getStakingPool(); - console.log("User", _user, "claimed", _amount); - - PendingRewardFields storage _reward = _getPendingRewardFields(_user); - console.log("claimReward: \t => (+)=", _reward.debited, _reward.debited); - console.log("claimReward: \t => (-)=", _reward.credited, _reward.credited + _amount); - // _reward.debited = 0; - _reward.credited += _amount; - _reward.lastSyncBlock = block.number; - console.log("claimReward: \t", _balance, _sPool.accumulatedRps); - - SettledRewardFields storage _sReward = _getSettledRewardFields(_user); - _sReward.debited = 0; - _sReward.accumulatedRps = _sPool.accumulatedRps; - } - - function _slashed(PendingPool memory, uint256 _epoch) internal view override returns (bool) { - return _epochSlashed[_epoch]; - } - - function _blockNumberToEpoch(uint256 _block) internal view override returns (uint256 _epoch) { - for (uint256 _i; _i < _epoches.length; _i++) { - if (_block >= _epoches[_i]) { - _epoch = _i + 1; - } - } - } - - function _getPendingPool() internal view override returns (PendingPool memory) { - return pool; - } - - function _getSettledPool() internal view override returns (SettledPool memory) { - return sPool; - } - - function balanceOf(address _user) public view override returns (uint256) { - return _stakingBalance[_user]; - } -} diff --git a/contracts/mocks/MockStaking.sol b/contracts/mocks/MockStaking.sol new file mode 100644 index 000000000..a99486e30 --- /dev/null +++ b/contracts/mocks/MockStaking.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../staking/RewardCalculation.sol"; + +contract MockStaking is RewardCalculation { + /// @dev Mapping from user => staking balance + mapping(address => uint256) internal _stakingBalance; + /// @dev Mapping from period number => slashed + mapping(uint256 => bool) internal _periodSlashed; + uint256[] internal _periods; + uint256 internal _totalBalance; + + address public poolAddr; + + constructor(address _poolAddr) { + _periods.push(0); + poolAddr = _poolAddr; + } + + function endPeriod() external { + _periods.push(block.number); + } + + function stake(address _user, uint256 _amount) external { + uint256 _balance = _stakingBalance[_user]; + uint256 _newBalance = _balance + _amount; + _syncUserReward(poolAddr, _user, _newBalance); + _stakingBalance[_user] = _newBalance; + _totalBalance += _amount; + } + + function unstake(address _user, uint256 _amount) external { + uint256 _balance = _stakingBalance[_user]; + uint256 _newBalance = _balance - _amount; + _syncUserReward(poolAddr, _user, _newBalance); + _stakingBalance[_user] = _newBalance; + _totalBalance -= _amount; + } + + function slash() external { + uint256 _period = getPeriod(); + _periodSlashed[_period] = true; + _onSlashed(poolAddr); + } + + function recordReward(uint256 _rewardAmount) external { + _recordReward(poolAddr, _rewardAmount); + } + + function commitRewardPool() external { + _onPoolSettled(poolAddr); + } + + function increaseAccumulatedRps(uint256 _amount) external { + _recordReward(poolAddr, _amount); + } + + function getPeriod() public view returns (uint256) { + return _periodOf(block.number); + } + + function claimReward(address _user) external returns (uint256 _amount) { + _amount = _claimReward(poolAddr, _user); + } + + function balanceOf(address, address _user) public view override returns (uint256) { + return _stakingBalance[_user]; + } + + function totalBalance(address) public view virtual override returns (uint256) { + return _totalBalance; + } + + function _slashed(address, uint256 _period) internal view override returns (bool) { + return _periodSlashed[_period]; + } + + function _periodOf(uint256 _block) internal view override returns (uint256 _period) { + for (uint256 _i; _i < _periods.length; _i++) { + if (_block >= _periods[_i]) { + _period = _i + 1; + } + } + } +} diff --git a/contracts/staking/CoreStaking.sol b/contracts/staking/CoreStaking.sol deleted file mode 100644 index 248c21260..000000000 --- a/contracts/staking/CoreStaking.sol +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -// import "hardhat/console.sol"; - -abstract contract CoreStaking { - struct PendingRewardFields { - // Recorded reward amount. - uint256 debited; - // The amount rewards that user have already earned. - uint256 credited; - // Last block number that the info updated. - uint256 lastSyncBlock; - } - - struct SettledRewardFields { - // The balance at the commit time. - uint256 balance; - // Recorded reward amount. - uint256 debited; - // Accumulated of the amount rewards per share (one unit staking). - uint256 accumulatedRps; - } - - struct PendingPool { - // Last block number that the info updated. - uint256 lastSyncBlock; - // Accumulated of the amount rewards per share (one unit staking). - uint256 accumulatedRps; - } - - struct SettledPool { - // Last block number that the info updated. - uint256 lastSyncBlock; - // Accumulated of the amount rewards per share (one unit staking). - uint256 accumulatedRps; - } - - /// @dev Mapping from the user address => settled reward info of the user - mapping(address => SettledRewardFields) internal _sUserReward; - /// @dev Mapping from the user address => pending reward info of the user - mapping(address => PendingRewardFields) internal _pUserReward; - - /** - * @dev Syncs the user reward. - * - * Emits the `` event and the `` event. - * - * @notice The method should be called whenever the user's balance changes. - * - * TODO: add test to stake/unstake many times in block. - * TODO: add events. - * - */ - function _syncUserReward(address _user, uint256 _newBalance) internal { - PendingRewardFields storage _reward = _getPendingRewardFields(_user); - SettledPool memory _sPool = _getSettledPool(); - - // Syncs the reward once the last sync is settled. - if (_reward.lastSyncBlock <= _sPool.lastSyncBlock) { - SettledRewardFields storage _sReward = _getSettledRewardFields(_user); - uint256 _claimableReward = getClaimableReward(_user); - // console.log("Synced for", _user, _claimableReward, _sPool.accumulatedRps); - _sReward.balance = balanceOf(_user); - _sReward.debited = _claimableReward; - _sReward.accumulatedRps = _sPool.accumulatedRps; - } - - PendingPool memory _pool = _getPendingPool(); - _reward.debited = getTotalReward(_user); - _reward.credited = (_newBalance * _pool.accumulatedRps) / 1e18; - _reward.lastSyncBlock = block.number; - } - - /** - * @dev Returns the total reward. - */ - function getTotalReward(address _user) public view returns (uint256) { - PendingRewardFields memory _reward = _getPendingRewardFields(_user); - PendingPool memory _pool = _getPendingPool(); - - uint256 _balance = balanceOf(_user); - if (_slashed(_pool, _blockNumberToEpoch(_reward.lastSyncBlock))) { - SettledRewardFields memory _sReward = _getSettledRewardFields(_user); - uint256 _credited = (_sReward.accumulatedRps * _balance) / 1e18; - return (_balance * _pool.accumulatedRps) / 1e18 + _sReward.debited - _credited; - } - - // console.log("_getUserReward:\t", _balance, _pool.accumulatedRps, _reward.debited); - // console.log("_getUserReward:\t", _reward.credited); - return (_balance * _pool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; - } - - function getClaimableReward(address _user) public view returns (uint256) { - PendingRewardFields memory _reward = _getPendingRewardFields(_user); - SettledRewardFields memory _sReward = _getSettledRewardFields(_user); - SettledPool memory _sPool = _getSettledPool(); - - // console.log("-> getClaimableReward", _user, _reward.lastSyncBlock, _sPool.lastSyncBlock); - if (_reward.lastSyncBlock <= _sPool.lastSyncBlock) { - // console.log("\t-> sync"); - uint256 _currentBalance = balanceOf(_user); - _sReward.debited = (_currentBalance * _sPool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; - _sReward.balance = _currentBalance; - _sReward.accumulatedRps = _sPool.accumulatedRps; - } - - uint256 _balance = _sReward.balance; - uint256 _credited = (_balance * _sReward.accumulatedRps) / 1e18; - // console.log("\t", _balance, _sReward.accumulatedRps, _sPool.accumulatedRps); - // console.log("\t", _sReward.debited); - return (_balance * _sPool.accumulatedRps) / 1e18 + _sReward.debited - _credited; - } - - /** - * @dev Returns the pending reward. - */ - function getPendingReward(address _user) external view returns (uint256 _amount) { - _amount = getTotalReward(_user) - getClaimableReward(_user); - } - - /** - * @dev Claims the settled reward for a specific user. - */ - function _claimReward(address _user) public returns (uint256 _amount) { - // uint256 _balance = balanceOf(_user); - _amount = getClaimableReward(_user); - SettledPool memory _sPool = _getSettledPool(); - // PendingPool memory _pool = _getStakingPool(); - // console.log("User", _user, "claimed", _amount); - - PendingRewardFields storage _reward = _getPendingRewardFields(_user); - // console.log("claimReward: \t => (+)=", _reward.debited, _reward.debited); - // console.log("claimReward: \t => (-)=", _reward.credited, _reward.credited + _amount); - // _reward.debited = 0; - _reward.credited += _amount; - _reward.lastSyncBlock = block.number; - // console.log("claimReward: \t", _balance, _sPool.accumulatedRps); - - SettledRewardFields storage _sReward = _getSettledRewardFields(_user); - _sReward.debited = 0; - _sReward.accumulatedRps = _sPool.accumulatedRps; - } - - /** - * @dev Returns the pending pool. - */ - function _getPendingPool() internal view virtual returns (PendingPool memory) {} - - /** - * @dev Returns settled pool. - */ - function _getSettledPool() internal view virtual returns (SettledPool memory) {} - - /** - * @dev Returns the pending reward info of a specific user. - */ - function _getPendingRewardFields(address _user) internal view virtual returns (PendingRewardFields storage) { - return _pUserReward[_user]; - } - - /** - * @dev Returns the settled reward info of a specific user. - */ - function _getSettledRewardFields(address _user) internal view virtual returns (SettledRewardFields storage) { - return _sUserReward[_user]; - } - - /** - * @dev Returns whether the pool is slashed in the epoch `_epoch`. - */ - function _slashed(PendingPool memory, uint256 _epoch) internal view virtual returns (bool); - - /** - * @dev Returns the epoch from the block number. - */ - function _blockNumberToEpoch(uint256) internal view virtual returns (uint256); - - /** - * @dev Returns the staking amount of the user. - */ - function balanceOf(address) public view virtual returns (uint256); -} diff --git a/contracts/staking/RewardCalculation.sol b/contracts/staking/RewardCalculation.sol new file mode 100644 index 000000000..331d032b7 --- /dev/null +++ b/contracts/staking/RewardCalculation.sol @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +/** + * @title RewardCalculation contract + * @dev This contract mainly contains to calculate reward for staking contract. + * + * TODO: optimize gas cost when emitting SettledRewardUpdated and PendingRewardUpdated in the method `_claimReward`; + * + */ +abstract contract RewardCalculation { + /// @dev Emitted when the settled pool is updated. + event SettledPoolUpdated(address poolAddress, uint256 accumulatedRps); + /// @dev Emitted when the pending pool is updated. + event PendingPoolUpdated(address poolAddress, uint256 accumulatedRps); + /// @dev Emitted when the fields to calculate settled reward for the user is updated. + event SettledRewardUpdated( + address poolAddress, + address user, + uint256 balance, + uint256 debited, + uint256 accumulatedRps + ); + /// @dev Emitted when the fields to calculate pending reward for the user is updated. + event PendingRewardUpdated(address poolAddress, address user, uint256 debited, uint256 credited); + + struct PendingRewardFields { + // Recorded reward amount. + uint256 debited; + // The amount rewards that user have already earned. + uint256 credited; + // Last block number that the info updated. + uint256 lastSyncBlock; + } + + struct SettledRewardFields { + // The balance at the commit time. + uint256 balance; + // Recorded reward amount. + uint256 debited; + // Accumulated of the amount rewards per share (one unit staking). + uint256 accumulatedRps; + } + + struct PendingPool { + // Last block number that the info updated. + uint256 lastSyncBlock; + // Accumulated of the amount rewards per share (one unit staking). + uint256 accumulatedRps; + } + + struct SettledPool { + // Last block number that the info updated. + uint256 lastSyncBlock; + // Accumulated of the amount rewards per share (one unit staking). + uint256 accumulatedRps; + } + + /// @dev Mapping from the pool address => user address => settled reward info of the user + mapping(address => mapping(address => SettledRewardFields)) internal _sUserReward; + /// @dev Mapping from the pool address => user address => pending reward info of the user + mapping(address => mapping(address => PendingRewardFields)) internal _pUserReward; + + /// @dev Mapping from the pool address => pending pool data + mapping(address => PendingPool) internal _pendingPool; + /// @dev Mapping from the pool address => settled pool data + mapping(address => SettledPool) internal _settledPool; + + /** + * @dev Returns total rewards from scratch including pending reward and claimable reward except the claimed amount. + * + * @notice Do not use this function to get claimable reward, consider using the method `getClaimableReward` instead. + * + */ + function getTotalReward(address _poolAddr, address _user) public view returns (uint256) { + PendingRewardFields memory _reward = _pUserReward[_poolAddr][_user]; + PendingPool memory _pool = _pendingPool[_poolAddr]; + + uint256 _balance = balanceOf(_poolAddr, _user); + if (_slashed(_poolAddr, _periodOf(_reward.lastSyncBlock))) { + SettledRewardFields memory _sReward = _sUserReward[_poolAddr][_user]; + uint256 _credited = (_sReward.accumulatedRps * _balance) / 1e18; + return (_balance * _pool.accumulatedRps) / 1e18 + _sReward.debited - _credited; + } + + return (_balance * _pool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; + } + + /** + * @dev Returns the reward amount that user claimable. + */ + function getClaimableReward(address _poolAddr, address _user) public view returns (uint256) { + PendingRewardFields memory _reward = _pUserReward[_poolAddr][_user]; + SettledRewardFields memory _sReward = _sUserReward[_poolAddr][_user]; + SettledPool memory _sPool = _settledPool[_poolAddr]; + + if (_reward.lastSyncBlock <= _sPool.lastSyncBlock) { + uint256 _currentBalance = balanceOf(_poolAddr, _user); + _sReward.debited = (_currentBalance * _sPool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; + _sReward.balance = _currentBalance; + _sReward.accumulatedRps = _sPool.accumulatedRps; + } + + uint256 _balance = _sReward.balance; + uint256 _credited = (_balance * _sReward.accumulatedRps) / 1e18; + return (_balance * _sPool.accumulatedRps) / 1e18 + _sReward.debited - _credited; + } + + /** + * @dev Returns the pending reward. + */ + function getPendingReward(address _poolAddr, address _user) external view returns (uint256 _amount) { + _amount = getTotalReward(_poolAddr, _user) - getClaimableReward(_poolAddr, _user); + } + + /** + * @dev Returns the staking amount of the user. + */ + function balanceOf(address _poolAddr, address _user) public view virtual returns (uint256); + + /** + * @dev Returns the total staking amount of all users. + */ + function totalBalance(address _poolAddr) public view virtual returns (uint256); + + /** + * @dev Syncs the user reward. + * + * Emits the `SettledRewardUpdated` event if the last block user made changes is recorded in the settled period. + * Emits the `PendingRewardUpdated` event. + * + * @notice The method should be called whenever the user's balance changes. + * + */ + function _syncUserReward( + address _poolAddr, + address _user, + uint256 _newBalance + ) internal { + PendingRewardFields storage _reward = _pUserReward[_poolAddr][_user]; + SettledPool memory _sPool = _settledPool[_poolAddr]; + + // Syncs the reward once the last sync is settled. + if (_reward.lastSyncBlock <= _sPool.lastSyncBlock) { + uint256 _claimableReward = getClaimableReward(_poolAddr, _user); + uint256 _balance = balanceOf(_poolAddr, _user); + + SettledRewardFields storage _sReward = _sUserReward[_poolAddr][_user]; + _sReward.balance = _balance; + _sReward.debited = _claimableReward; + _sReward.accumulatedRps = _sPool.accumulatedRps; + emit SettledRewardUpdated(_poolAddr, _user, _balance, _claimableReward, _sPool.accumulatedRps); + } + + PendingPool memory _pool = _pendingPool[_poolAddr]; + uint256 _debited = getTotalReward(_poolAddr, _user); + uint256 _credited = (_newBalance * _pool.accumulatedRps) / 1e18; + + _reward.debited = _debited; + _reward.credited = _credited; + _reward.lastSyncBlock = block.number; + emit PendingRewardUpdated(_poolAddr, _user, _debited, _credited); + } + + /** + * @dev Claims the settled reward for a specific user. + * + * Emits the `PendingRewardUpdated` event and the `SettledRewardUpdated` event. + * + * @notice This method should be called before transferring rewards for the user. + * + */ + function _claimReward(address _poolAddr, address _user) public returns (uint256 _amount) { + _amount = getClaimableReward(_poolAddr, _user); + SettledPool memory _sPool = _settledPool[_poolAddr]; + + PendingRewardFields storage _reward = _pUserReward[_poolAddr][_user]; + _reward.credited += _amount; + _reward.lastSyncBlock = block.number; + emit PendingRewardUpdated(_poolAddr, _user, _reward.debited, _reward.credited); + + SettledRewardFields storage _sReward = _sUserReward[_poolAddr][_user]; + _sReward.debited = 0; + _sReward.accumulatedRps = _sPool.accumulatedRps; + emit SettledRewardUpdated(_poolAddr, _user, _sReward.balance, 0, _sPool.accumulatedRps); + } + + /** + * @dev Records the amount of reward `_reward` for the pending pool `_poolAddr`. + * + * Emits the `PendingPoolUpdated` event. + * + * @notice This method should not be called after the pool is slashed. + * + */ + function _recordReward(address _poolAddr, uint256 _reward) internal { + PendingPool storage _pool = _pendingPool[_poolAddr]; + uint256 _accumulatedRps = _pool.accumulatedRps + (_reward * 1e18) / totalBalance(_poolAddr); + _pool.accumulatedRps = _accumulatedRps; + _pool.lastSyncBlock = block.number; + emit PendingPoolUpdated(_poolAddr, _accumulatedRps); + } + + /** + * @dev Handles when the pool `_poolAddr` is slashed. + * + * Emits the `PendingPoolUpdated` event. + * + * @notice This method should be called when the pool is slashed. + * + */ + function _onSlashed(address _poolAddr) internal { + uint256 _accumulatedRps = _settledPool[_poolAddr].accumulatedRps; + PendingPool storage _pool = _pendingPool[_poolAddr]; + _pool.accumulatedRps = _accumulatedRps; + _pool.lastSyncBlock = block.number; + emit PendingPoolUpdated(_poolAddr, _accumulatedRps); + } + + /** + * @dev Handles when the pool `_poolAddr` is settled. + * + * Emits the `SettledPoolUpdated` event. + * + * @notice This method should be called once in the end of each period. + * + */ + function _onPoolSettled(address _poolAddr) internal { + uint256 _accumulatedRps = _pendingPool[_poolAddr].accumulatedRps; + SettledPool storage _sPool = _settledPool[_poolAddr]; + _sPool.accumulatedRps = _accumulatedRps; + _sPool.lastSyncBlock = block.number; + emit SettledPoolUpdated(_poolAddr, _accumulatedRps); + } + + /** + * @dev Returns whether the pool is slashed in the period `_period`. + */ + function _slashed(address _poolAddr, uint256 _period) internal view virtual returns (bool); + + /** + * @dev Returns the period from the block number. + */ + function _periodOf(uint256 _block) internal view virtual returns (uint256); +} diff --git a/test/staking/CoreStaking.test.ts b/test/staking/CoreStaking.test.ts index 3ec0fa055..9f3187863 100644 --- a/test/staking/CoreStaking.test.ts +++ b/test/staking/CoreStaking.test.ts @@ -3,14 +3,15 @@ import { expect } from 'chai'; import { BigNumber, BigNumberish } from 'ethers'; import { ethers, network } from 'hardhat'; -import { MockManager, MockManager__factory } from '../../src/types'; +import { MockStaking, MockStaking__factory } from '../../src/types'; const EPS = 1; +const poolAddr = ethers.constants.AddressZero; let deployer: SignerWithAddress; let userA: SignerWithAddress; let userB: SignerWithAddress; -let manager: MockManager; +let stakingContract: MockStaking; const local = { accumulatedRewardForA: BigNumber.from(0), @@ -18,9 +19,9 @@ const local = { claimableRewardForA: BigNumber.from(0), claimableRewardForB: BigNumber.from(0), recordReward: async function (reward: BigNumberish) { - const totalStaked = await manager.totalBalance(); - const stakingAmountA = await manager.balanceOf(userA.address); - const stakingAmountB = await manager.balanceOf(userB.address); + const totalStaked = await stakingContract.totalBalance(poolAddr); + const stakingAmountA = await stakingContract.balanceOf(poolAddr, userA.address); + const stakingAmountB = await stakingContract.balanceOf(poolAddr, userB.address); this.accumulatedRewardForA = this.accumulatedRewardForA.add( BigNumber.from(reward).mul(stakingAmountA).div(totalStaked) ); @@ -54,24 +55,24 @@ const local = { const expectLocalCalculationRight = async () => { { - const userReward = await manager.getTotalReward(userA.address); + const userReward = await stakingContract.getTotalReward(poolAddr, userA.address); expect( userReward.sub(local.accumulatedRewardForA).abs().lte(EPS), `invalid user reward for A expected=${local.accumulatedRewardForA.toString()} actual=${userReward}` ).to.be.true; - const claimableReward = await manager.getClaimableReward(userA.address); + const claimableReward = await stakingContract.getClaimableReward(poolAddr, userA.address); expect( claimableReward.sub(local.claimableRewardForA).abs().lte(EPS), `invalid claimable reward for A expected=${local.claimableRewardForA.toString()} actual=${claimableReward}` ).to.be.true; } { - const userReward = await manager.getTotalReward(userB.address); + const userReward = await stakingContract.getTotalReward(poolAddr, userB.address); expect( userReward.sub(local.accumulatedRewardForB).abs().lte(EPS), `invalid user reward for B expected=${local.accumulatedRewardForB.toString()} actual=${userReward}` ).to.be.true; - const claimableReward = await manager.getClaimableReward(userB.address); + const claimableReward = await stakingContract.getClaimableReward(poolAddr, userB.address); expect( claimableReward.sub(local.claimableRewardForB).abs().lte(EPS), `invalid claimable reward for B expected=${local.claimableRewardForB.toString()} actual=${claimableReward}` @@ -82,83 +83,83 @@ const expectLocalCalculationRight = async () => { describe('Core Staking test', () => { before(async () => { [deployer, userA, userB] = await ethers.getSigners(); - manager = await new MockManager__factory(deployer).deploy(); + stakingContract = await new MockStaking__factory(deployer).deploy(poolAddr); await network.provider.send('evm_setAutomine', [false]); local.reset(); }); - it('Should be able to stake/unstake and get right total reward for a successful epoch', async () => { - await manager.stake(userA.address, 100); - await manager.stake(userB.address, 100); + it('Should work properly with staking actions occurring sequentially for a normal period', async () => { + await stakingContract.stake(userA.address, 100); + await stakingContract.stake(userB.address, 100); await network.provider.send('evm_mine'); - await manager.recordReward(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await manager.recordReward(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await network.provider.send('evm_mine'); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await manager.stake(userA.address, 200); + await stakingContract.stake(userA.address, 200); await network.provider.send('evm_mine'); await expectLocalCalculationRight(); - await manager.recordReward(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await manager.unstake(userA.address, 200); - await manager.recordReward(1000); + await stakingContract.unstake(userA.address, 200); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await manager.stake(userA.address, 200); + await stakingContract.stake(userA.address, 200); await network.provider.send('evm_mine'); await local.recordReward(0); await expectLocalCalculationRight(); - await manager.commitRewardPool(); - await manager.endEpoch(); + await stakingContract.commitRewardPool(); + await stakingContract.endPeriod(); await network.provider.send('evm_mine'); local.commitRewardPool(); await expectLocalCalculationRight(); }); - it('Should be able to stake/unstake and get right total reward for a slashed epoch', async () => { - await manager.stake(userA.address, 100); + it('Should work properly with staking actions occurring sequentially for a slashed period', async () => { + await stakingContract.stake(userA.address, 100); await network.provider.send('evm_mine'); await expectLocalCalculationRight(); await local.recordReward(0); - await manager.recordReward(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await manager.recordReward(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await manager.stake(userA.address, 300); - await manager.recordReward(1000); + await stakingContract.stake(userA.address, 300); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await manager.slash(); + await stakingContract.slash(); await network.provider.send('evm_mine'); local.slash(); await expectLocalCalculationRight(); - await manager.recordReward(0); + await stakingContract.recordReward(0); await network.provider.send('evm_mine'); await local.recordReward(0); await expectLocalCalculationRight(); @@ -168,48 +169,48 @@ describe('Core Staking test', () => { await network.provider.send('evm_mine'); await network.provider.send('evm_mine'); - await manager.unstake(userA.address, 300); + await stakingContract.unstake(userA.address, 300); await network.provider.send('evm_mine'); - await manager.unstake(userA.address, 100); + await stakingContract.unstake(userA.address, 100); await network.provider.send('evm_mine'); await expectLocalCalculationRight(); - await manager.commitRewardPool(); - await manager.endEpoch(); + await stakingContract.commitRewardPool(); + await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await expectLocalCalculationRight(); }); - it('Should be able to stake/unstake/claim and get right total reward for a slashed epoch again', async () => { - await manager.stake(userA.address, 100); + it('Should work properly with staking actions occurring sequentially for a slashed period again', async () => { + await stakingContract.stake(userA.address, 100); await network.provider.send('evm_mine'); await local.recordReward(0); await expectLocalCalculationRight(); - await manager.claimReward(userA.address); - await manager.recordReward(1000); + await stakingContract.claimReward(userA.address); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); local.claimRewardForA(); await expectLocalCalculationRight(); - await manager.recordReward(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await manager.stake(userA.address, 300); - await manager.recordReward(1000); + await stakingContract.stake(userA.address, 300); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await manager.slash(); + await stakingContract.slash(); await network.provider.send('evm_mine'); local.slash(); await expectLocalCalculationRight(); - await manager.recordReward(0); + await stakingContract.recordReward(0); await network.provider.send('evm_mine'); await local.recordReward(0); await expectLocalCalculationRight(); @@ -219,95 +220,176 @@ describe('Core Staking test', () => { await network.provider.send('evm_mine'); await network.provider.send('evm_mine'); - await manager.unstake(userA.address, 300); + await stakingContract.unstake(userA.address, 300); await network.provider.send('evm_mine'); - await manager.unstake(userA.address, 100); + await stakingContract.unstake(userA.address, 100); await network.provider.send('evm_mine'); await expectLocalCalculationRight(); - await manager.claimReward(userB.address); - await manager.commitRewardPool(); - await manager.endEpoch(); + await stakingContract.claimReward(userB.address); + await stakingContract.commitRewardPool(); + await stakingContract.endPeriod(); await network.provider.send('evm_mine'); local.claimRewardForB(); await expectLocalCalculationRight(); }); - it('Should be able to get right claimable reward for the committed epoch', async () => { - await manager.recordReward(1000); - await manager.commitRewardPool(); - await manager.endEpoch(); + it('Should be able to calculate right reward after claiming', async () => { + await stakingContract.recordReward(1000); + await stakingContract.commitRewardPool(); + await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await local.recordReward(1000); local.commitRewardPool(); await expectLocalCalculationRight(); - await manager.claimReward(userA.address); - await manager.recordReward(1000); + await stakingContract.claimReward(userA.address); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); local.claimRewardForA(); await expectLocalCalculationRight(); - await manager.claimReward(userA.address); + await stakingContract.claimReward(userA.address); await network.provider.send('evm_mine'); local.claimRewardForA(); - console.log(local, await ethers.provider.getBlockNumber()); await expectLocalCalculationRight(); - await manager.claimReward(userB.address); - await manager.commitRewardPool(); - await manager.endEpoch(); + await stakingContract.claimReward(userB.address); + await stakingContract.commitRewardPool(); + await stakingContract.endPeriod(); await network.provider.send('evm_mine'); local.claimRewardForB(); local.commitRewardPool(); await expectLocalCalculationRight(); }); - it('Should be able to get right claimable reward test', async () => { - console.log(0, local); - await manager.stake(userA.address, 100); + it('Should work properly with staking actions from multi-users occurring in the same block', async () => { + await stakingContract.stake(userA.address, 100); await network.provider.send('evm_mine'); - console.log(1, local); - await manager.stake(userA.address, 300); - await manager.recordReward(1000); + await stakingContract.stake(userA.address, 300); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - console.log(2, local); - await manager.stake(userB.address, 200); - await manager.recordReward(1000); + await stakingContract.stake(userB.address, 200); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await manager.recordReward(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await manager.recordReward(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await manager.unstake(userB.address, 200); - await manager.unstake(userA.address, 400); - await manager.recordReward(1000); + await stakingContract.unstake(userB.address, 200); + await stakingContract.unstake(userA.address, 400); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - console.log(3, local); - await manager.claimReward(userA.address); - await manager.claimReward(userB.address); + await stakingContract.claimReward(userA.address); + await stakingContract.claimReward(userB.address); await network.provider.send('evm_mine'); local.claimRewardForA(); local.claimRewardForB(); await expectLocalCalculationRight(); - console.log(4, local); + await stakingContract.unstake(userA.address, 200); + await stakingContract.commitRewardPool(); + await stakingContract.endPeriod(); + await network.provider.send('evm_mine'); + local.commitRewardPool(); + await expectLocalCalculationRight(); + + await stakingContract.claimReward(userA.address); + await stakingContract.claimReward(userB.address); + await network.provider.send('evm_mine'); + local.claimRewardForA(); + local.claimRewardForB(); + await expectLocalCalculationRight(); + }); + + it('Should work properly with staking actions occurring in the same block', async () => { + await stakingContract.stake(userA.address, 100); + await stakingContract.unstake(userA.address, 100); + await stakingContract.stake(userA.address, 100); + await stakingContract.unstake(userA.address, 100); + await stakingContract.stake(userB.address, 200); + await stakingContract.unstake(userB.address, 200); + await stakingContract.stake(userB.address, 200); + await stakingContract.unstake(userB.address, 200); + await stakingContract.stake(userB.address, 200); + await stakingContract.stake(userA.address, 100); + await stakingContract.unstake(userA.address, 100); + await stakingContract.unstake(userB.address, 200); + await stakingContract.stake(userB.address, 200); + await stakingContract.unstake(userA.address, 100); + await stakingContract.stake(userA.address, 100); + await stakingContract.unstake(userB.address, 200); + await stakingContract.recordReward(1000); + await stakingContract.commitRewardPool(); + await stakingContract.endPeriod(); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + local.commitRewardPool(); + await expectLocalCalculationRight(); + + await stakingContract.recordReward(1000); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await stakingContract.slash(); + await network.provider.send('evm_mine'); + local.slash(); + await expectLocalCalculationRight(); + + await stakingContract.commitRewardPool(); + await stakingContract.endPeriod(); + await network.provider.send('evm_mine'); + await expectLocalCalculationRight(); + + await stakingContract.recordReward(1000); + await stakingContract.commitRewardPool(); + await stakingContract.endPeriod(); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + local.commitRewardPool(); + await expectLocalCalculationRight(); + + await stakingContract.recordReward(1000); + await stakingContract.commitRewardPool(); + await stakingContract.endPeriod(); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + local.commitRewardPool(); + await expectLocalCalculationRight(); + + await stakingContract.claimReward(userA.address); + await stakingContract.claimReward(userB.address); + await stakingContract.claimReward(userA.address); + await stakingContract.claimReward(userB.address); + await stakingContract.claimReward(userA.address); + await stakingContract.claimReward(userB.address); + await stakingContract.claimReward(userA.address); + await stakingContract.claimReward(userA.address); + await stakingContract.claimReward(userA.address); + await stakingContract.claimReward(userB.address); + await stakingContract.claimReward(userB.address); + await stakingContract.claimReward(userB.address); + await network.provider.send('evm_mine'); + local.claimRewardForA(); + local.claimRewardForB(); + await expectLocalCalculationRight(); }); }); From 344c2cc8be40a5d2999c8355d9b03ce609b61d18 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Sat, 3 Sep 2022 18:09:58 +0700 Subject: [PATCH 037/190] [Staking] Finalize staking contract, rename some variable from other contracts & tests --- contracts/Staking.sol | 293 ------------------ contracts/ValidatorSet.sol | 8 +- contracts/interfaces/IStaking.sol | 239 ++++++++++---- contracts/mocks/MockSwapStorage.sol | 16 +- contracts/staking/DPoStaking.sol | 228 ++++++++++++++ contracts/staking/RewardCalculation.sol | 5 +- contracts/staking/StakingManager.sol | 359 ++++++++++++++++++++++ tests/sorting/MockSwapStorage.test.ts | 4 +- tests/validators/ValidatorSetCore.test.ts | 4 +- 9 files changed, 781 insertions(+), 375 deletions(-) delete mode 100644 contracts/Staking.sol create mode 100644 contracts/staking/DPoStaking.sol create mode 100644 contracts/staking/StakingManager.sol diff --git a/contracts/Staking.sol b/contracts/Staking.sol deleted file mode 100644 index b4200f1ce..000000000 --- a/contracts/Staking.sol +++ /dev/null @@ -1,293 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "./interfaces/IStaking.sol"; -import "./interfaces/IValidatorSet.sol"; -import "./libraries/Sorting.sol"; - -contract Staking is IStaking, Initializable { - /// TODO: expose fn to get validator info by validator address. - /// @dev Mapping from consensus address => validator index in `validatorCandidates`. - mapping(address => uint256) internal _validatorIndexes; - /// TODO: expose fn returns the whole validator arry. - /// @dev Validator array. - ValidatorCandidate[] public validatorCandidates; - - /// @dev Mapping from delegator address => consensus address => delegated amount. - mapping(address => mapping(address => uint256)) delegatedAmount; - - // TODO: expose this fn in the interface - /// @dev Configuration of maximum number of validator - uint256 public totalValidatorThreshold; - - // TODO: expose this fn in the interface - /// @dev Configuration of number of blocks that validator has to wait before unstaking, counted from staking time - uint256 public unstakingOnHoldBlocksNum; - - // TODO: expose this fn in the interface - /// @dev Configuration of minimum balance for being a validator - uint256 public minValidatorBalance; - - uint256[] internal currentValidatorIndexes; - - IValidatorSet validatorSetContract; - - uint256 public numOfCabinets; - - modifier onlyValidatorSetContract() { - require(msg.sender == address(validatorSetContract), "Only validator set contract"); - _; - } - - constructor() { - _disableInitializers(); - } - - /** - * @dev Initializes the contract storage. - */ - function initialize() external initializer { - /// TODO: bring this constant variable to params. - totalValidatorThreshold = 50; - unstakingOnHoldBlocksNum = 28800; // 28800 blocks ~= 1 day - minValidatorBalance = 3 * 1e6 * 1e18; // 3m RON - /// Add empty validator at 0-index - validatorCandidates.push(); - } - - /** - * @dev See {IStaking-proposeValidator}. - */ - function proposeValidator( - address _consensusAddr, - address payable _treasuryAddr, - uint256 _commissionRate - ) external payable returns (uint256 _candidateIdx) { - uint256 _amount = msg.value; - address _stakingAddr = msg.sender; - - require(validatorCandidates.length < totalValidatorThreshold, "Staking: query for exceeded validator array length"); - require(_validatorIndexes[_consensusAddr] == 0, "Staking: query for existed candidate"); - require(_amount > minValidatorBalance, "Staking: insuficient amount"); - require(_treasuryAddr.send(0), "Staking: invalid treasury address"); - - _candidateIdx = validatorCandidates.length; - ValidatorCandidate storage _candidate = validatorCandidates.push(); - _candidate.consensusAddr = _consensusAddr; - _candidate.stakingAddr = _stakingAddr; - _candidate.treasuryAddr = _treasuryAddr; - _candidate.commissionRate = _commissionRate; - _candidate.stakedAmount = _amount; - - emit ValidatorProposed(_consensusAddr, _stakingAddr, _amount, _candidate); - } - - /** - * @dev See {IStaking-stake}. - */ - function stake(address _consensusAddr) external payable { - uint256 _amount = msg.value; - ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); - require(_candidate.stakingAddr == msg.sender, "Staking: invalid staking address"); - - _candidate.stakedAmount += _amount; - emit Staked(_consensusAddr, _amount); - } - - /** - * @dev See {IStaking-unstake}. - */ - function unstake(address _consensusAddr, uint256 _amount) external { - ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); - require(_candidate.stakingAddr == msg.sender, "Staking: invalid staking address"); - require(_amount < _candidate.stakedAmount, "Staking: insufficient staked amount"); - - uint256 remainAmount = _candidate.stakedAmount - _amount; - require(remainAmount >= minValidatorBalance, "Staking: invalid staked amount left"); - - _candidate.stakedAmount = _amount; - emit Unstaked(_consensusAddr, _amount); - } - - /** - * @dev See {IStaking-delegate}. - */ - function delegate(address _consensusAddr) public payable { - _delegate(msg.sender, _consensusAddr, msg.value); - } - - /** - * @dev See {IStaking-delegate}. - */ - function undelegate(address _consensusAddr, uint256 _amount) public { - _undelegate(msg.sender, _consensusAddr, _amount); - require(payable(msg.sender).send(_amount), "Staking: could not transfer RON"); - } - - /** - * @dev TODO: move to IStaking.sol - */ - function redelegate( - address _consensusAddrSrc, - address _consensusAddrDst, - uint256 _amount - ) external { - _undelegate(msg.sender, _consensusAddrSrc, _amount); - _delegate(msg.sender, _consensusAddrDst, _amount); - } - - /** - * @dev TODO - */ - function getRewards(address[] calldata _consensusAddrList) - external - view - returns (uint256[] memory _pending, uint256[] memory _claimable) - { - revert("Unimplemented"); - } - - /** - * @dev Claims rewards. - */ - function claimRewards(address[] calldata _consensusAddrList, uint256 _amounts) external returns (uint256 _amount) { - revert("Unimplemented"); - } - - /** - * @dev Claims all pending rewards and delegates them to the consensus address. - */ - function delegateRewards(address[] calldata _consensusAddrList, address _consensusAddrDst) - external - returns (uint256 _amount) - { - revert("Unimplemented"); - } - - function onDeposit() external payable override { - revert("Unimplemented"); - } - - function allocateReward(address valAddr, uint256 amount) external override { - revert("Unimplemented"); - } - - function receiveReward(address valAddr) external payable override { - revert("Unimplemented"); - } - - function claimReward(uint256 amount) external override { - revert("Unimplemented"); - } - - function slash(address valAddr, uint256 amount) external override { - revert("Unimplemented"); - } - - /** - * @dev Delegates from a validator address. - * - * Requirements: - * - The validator is an existed candidate. - * - * Emits the `Delegated` event. - * - * @notice This function does not verify the `msg.value` with the amount. - */ - function _delegate( - address _delegator, - address _consensusAddr, - uint256 _amount - ) internal { - ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); - _candidate.delegatedAmount += _amount; - delegatedAmount[_delegator][_consensusAddr] += _amount; - - emit Delegated(_delegator, _consensusAddr, _amount); - } - - /** - * @dev Undelegates from a validator address. - * - * Requirements: - * - The validator is an existed candidate. - * - The delegated amount is larger than or equal to the undelegated amount. - * - * Emits the `Undelegated` event. - * - * @notice Consider transferring back the amount of RON after calling this function. - */ - function _undelegate( - address _delegator, - address _consensusAddr, - uint256 _amount - ) internal { - require(delegatedAmount[_delegator][_consensusAddr] >= _amount, "Staking: insufficient amount to undelegate"); - - ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); - _candidate.delegatedAmount -= _amount; - delegatedAmount[_delegator][_consensusAddr] -= _amount; - - emit Undelegated(_delegator, _consensusAddr, _amount); - } - - /** - * @dev Returns the validator candidate in form storage. - * - * Requirements: - * - The candidate is already existed. - * - */ - function _getCandidate(address _consensusAddr) internal view returns (ValidatorCandidate storage _candidate) { - uint256 _idx = _validatorIndexes[_consensusAddr]; - require(_idx > 0, "Staking: query for nonexistent candidate"); - _candidate = validatorCandidates[_idx]; - } - - /** - * @notice Update set of validators - * - * @dev Sorting the validators by their current balance, then pick the top N validators to be - * assigned to the new set. The result is returned to the `ValidatorSet` contract. - * - * Requirements: - * - Only validator and `ValidatorSet` contract can call this function - * - * @return newValidatorSet Validator set for the new epoch - */ - function updateValidatorSet() external returns (ValidatorCandidate[] memory newValidatorSet) { - uint _length = validatorCandidates.length; - Sorting.Node[] memory _nodes = new Sorting.Node[](_length); - Sorting.Node[] memory _sortedNodes = new Sorting.Node[](_length); - - for (uint i = 0; i < _length; i++) { - ValidatorCandidate memory _validator = validatorCandidates[i]; - - _nodes[i].key = i; - _nodes[i].value = _validator.stakedAmount + _validator.delegatedAmount; - } - - delete currentValidatorIndexes; - _sortedNodes = Sorting.sortNodes(_nodes); - - /// TODO: pick M validators which are governance - uint _currentSetSize = _length < numOfCabinets ? _length : numOfCabinets; - for (uint i = 0; i < _currentSetSize; i++) { - currentValidatorIndexes.push(_sortedNodes[i].value); - } - - return getCurrentValidatorSet(); - } - - function getCurrentValidatorSet() public view returns (ValidatorCandidate[] memory currentValidatorSet_) { - uint _length = currentValidatorIndexes.length; - currentValidatorSet_ = new ValidatorCandidate[](currentValidatorIndexes.length); - for (uint i = 0; i < _length; i++) { - currentValidatorSet_[i] = validatorCandidates[currentValidatorIndexes[i]]; - } - - return currentValidatorSet_; - } -} diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index 6f656bc67..d2486cc87 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -61,13 +61,13 @@ contract ValidatorSet is IValidatorSet, ValidatorSetCore { function updateValidators() external onlyValidator atEpochEnding returns (address[] memory) { // 1. fetch new validator set from staking contract - IStaking.ValidatorCandidate[] memory _upcommingValidatorSet = stakingContract.updateValidatorSet(); + IStaking.ValidatorCandidate[] memory _upcommingValidatorSet; // = stakingContract.updateValidatorSet(); require(_upcommingValidatorSet.length <= numOfCabinets, "Validators: Exceeds maximum validators per epoch"); // 2. update last updated lastUpdated = block.number; - // 3. update new validator set + // 3. update new validator set _doUpdateState(_upcommingValidatorSet); emit ValidatorSetUpdated(); @@ -90,7 +90,7 @@ contract ValidatorSet is IValidatorSet, ValidatorSetCore { if (_validator.jailed) { emit DeprecatedDeposit(_valAddr, _value); } else { - stakingContract.onDeposit(); + stakingContract.recordReward(_valAddr, _value); } } @@ -157,7 +157,7 @@ contract ValidatorSet is IValidatorSet, ValidatorSetCore { // current set: [ ===== ] // updating part: ^^^^^ for (uint256 i = 0; i < k; ++i) { - Validator memory _oldValidator = _getValidatorAtMiningIndex(i); + Validator memory _oldValidator = _getValidatorAtMiningIndex(i); if (!_isSameValidator(_incomingValidatorSet[i], _oldValidator)) { _setValidatorAtMiningIndex(i, _incomingValidatorSet[i]); } diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 1f8e024e7..2035b566a 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -4,11 +4,11 @@ pragma solidity ^0.8.9; interface IStaking { struct ValidatorCandidate { - /// @dev Address that stakes for the validator, each consensus address has only one staking address. - address stakingAddr; + /// @dev The candidate admin that stakes for the validator. + address candidateAdmin; /// @dev Address of the validator that produces block, e.g. block.coinbase. This is so-called validator address. address consensusAddr; - /// @dev Address that receives mining fee of the validator + /// @dev Address that receives mining reward of the validator address payable treasuryAddr; /// @dev The percentile of reward that validators can be received, the rest goes to the delegators uint256 commissionRate; @@ -34,119 +34,230 @@ interface IStaking { event Delegated(address indexed delegator, address indexed validator, uint256 amount); event Undelegated(address indexed delegator, address indexed validator, uint256 amount); - // TODO: write comment for this fn. - // TODO: write setter for this fn. + event GovernanceAdminContractUpdated(address); + event MinValidatorBalanceUpdated(uint256 threshold); + event MaxValidatorCandidateUpdated(uint256 threshold); + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR GOVERNANCE // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @dev Returns the governance admin contract address. + */ + function governanceAdminContract() external view returns (address); + + /** + * @dev Returns the minimum threshold for being a validator candidate. + */ function minValidatorBalance() external view returns (uint256); /** - * @dev Proposes a validator with detailed information. + * @dev Sets the minimum threshold for being a validator candidate. * * Requirements: - * - The `msg.value` is at least `MINIMUM_STAKING` amount. - * - TODO: update the requirements. + * - The method caller is governance admin. * - * Emits the `ValidatorProposed` event. + * Emits the `MinValidatorBalanceUpdated` event. + * + */ + function setMinValidatorBalance(uint256) external; + + /** + * @dev Returns the maximum number of validator candidate. + */ + function maxValidatorCandidate() external view returns (uint256); + + /** + * @dev Sets the maximum number of validator candidate. * - * @return index The index of new validator in the validator list + * Requirements: + * - The method caller is governance admin. + * + * Emits the `MaxValidatorCandidateUpdated` event. * */ - function proposeValidator( - address _consensusAddr, - address payable _treasuryAddr, - uint256 _commissionRate - ) external payable returns (uint256 index); + function setMaxValidatorCandidate(uint256) external; + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR VALIDATOR // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @dev Returns the validator candidate list. + */ + function getValidatorCandidates() external view returns (ValidatorCandidate[] memory candidates); /** - * @notice Stake as validator. + * @dev Records the amount of reward `_reward` for the pending pool `_poolAddr`. + * + * Requirements: + * - The method caller is validator contract. + * + * Emits the `PendingPoolUpdated` event. + * + * @notice This method should not be called after the pool is slashed. + * */ - function stake(uint256 amount) external payable; + function recordReward(address _consensusAddr, uint256 _reward) external; /** - * @notice Unstake as validator. Need to wait `UNSTAKING_ON_HOLD_BLOCKS_NUM` blocks. - * @dev The remain balance must either be greater than `MINIMUM_STAKING` value or equal to zero. + * @dev Settles the pending pool and allocates rewards for the validator. + * + * Requirements: + * - The method caller is validator contract. + * + * Emits the `SettledPoolUpdated` event. + * */ - function unstake(address consensusAddr, uint256 amount) external; + function settleRewardPool(address _consensusAddr) external; /** - * @notice Stake as delegator for a validator + * @dev Handles when the validator pool is slashed. + * + * Requirements: + * - The method caller is validator contract. + * + * Emits the `PendingPoolUpdated` event. * - * @dev Each delegator can stake token to many validators. Upon each delegation, the following - * actions are done: - * - Update staking balance of delegator for corresponding validator - * - Update staking balance of delegator - * - Not update `balance` of validator - * - Update `stakedDiff` of validator to record and update into `balance` at the end of each - * epoch in `updateValidatorSet()` */ - function delegate(address validatorAddress, uint256 amount) external payable; + function onValidatorSlashed(address _consensusAddr) external; /** - * @notice Unstake as delegator for a validator + * @dev Deducts from staking amount of the validator `_consensusAddr` for `_amount`. + * + * Requirements: + * - The method caller is validator contract. + * + * Emits the event `Unstaked` and `Undelegated` event. + * */ - function undelegate(address user, uint256 amount) external; + function deductStakingAmount(address _consensusAddr, uint256 _amount) external; + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR VALIDATOR CANDIDATE // + /////////////////////////////////////////////////////////////////////////////////////// /** - * @notice Update set of validators + * @dev Proposes a candidate to become a valdiator. * - * @dev Sorting the validators by their current balance, then pick the top N validators to be - * assigned to the new set. The result is returned to the `ValidatorSet` contract. There will be - * a threshold of M validator must be come from the governance. In case this threshold is set, - * M slots are reversed for the governing validator. The total of validators is configured in the - * `numOfCabinets` variable. If the actual number of validator candidates is not met, all of the - * candidates will become the validators. - * * Requirements: - * - Only validator and `ValidatorSet` contract can call this function - * - * @return newValidatorSet Validator set for the new epoch + * - The validator length is not exceeded the total validator threshold `maxValidatorCandidate`. + * - The amount is larger than or equal to the minimum validator balance `minValidatorBalance()`. + * + * Emits the `ValidatorProposed` event. + * + * @return _candidateIdx The index of the candidate in the validator candidate list. + * */ - function updateValidatorSet() external returns (ValidatorCandidate[] memory newValidatorSet); + function proposeValidator( + address _consensusAddr, + address payable _treasuryAddr, + uint256 _commissionRate + ) external payable returns (uint256 _candidateIdx); /** - * @notice Get current validator set. Number of current validator is set in `numOfCabinets`. - * - * @dev If the actual number of candidates is lower, all of the candidates are returned + * @dev Self-delegates to the validator candidate `_consensusAddr`. + * + * Requirements: + * - The candidate `_consensusAddr` is already existent. + * - The method caller is the candidate admin. + * - The `msg.value` is larger than 0. + * + * Emits the `Staked` event and the `Delegated` event. + * */ - function getCurrentValidatorSet() external returns (ValidatorCandidate[] memory currentValidatorSet); - + function stake(address _consensusAddr) external payable; + /** - * @dev Handle deposit request. Update validators' reward balance and delegators' balance. + * @dev Unstakes from the validator candidate `_consensusAddr` for `_amount`. * * Requirements: - * - Only `ValidatorSet` contract cann call this function + * - The candidate `_consensusAddr` is already existent. + * - The method caller is the candidate admin. + * + * Emits the `Unstaked` event and the `Undelegated` event. + * */ - function onDeposit() external payable; + function unstake(address _consensusAddr, uint256 _amount) external; /** - * @notice Distribute reward + * @dev Renounces being a validator candidate and takes back the delegated/staked amount. * - * @dev Allocate reward based on staked coins of delegators pendingReward. + * Requirements: + * - The candidate `_consensusAddr` is already existent. + * - The method caller is the candidate admin. + * + */ + function renounce(address consensusAddr) external; + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR DELEGATOR // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @dev Stakes for a validator candidate `_consensusAddr`. * * Requirements: - * - Only validator can call this function + * - The method caller is not the candidate admin. + * + * Emits the `Delegated` event. + * */ - function allocateReward(address valAddr, uint256 amount) external; + function delegate(address _consensusAddr) external payable; /** - * @notice Distribute reward + * @dev Unstakes from a validator candidate `_consensusAddr` for `_amount`. * - * @dev Get the reward from Validator.sol contract. Update pendingReward to claimableReward. + * Requirements: + * - The method caller is not the candidate admin. + * + * Emits the `Undelegated` event. + * + */ + function undelegate(address _consensusAddr, uint256 _amount) external; + + /** + * @dev Unstakes an amount of RON from the `_consensusAddrSrc` and stake for `_consensusAddrDst`. * * Requirements: - * - Only validator can call this function + * - The method caller is not the candidate admin. + * + * Emits the `Undelegated` event and the `Delegated` event. + * + */ + function redelegate( + address _consensusAddrSrc, + address _consensusAddrDst, + uint256 _amount + ) external; + + /** + * @dev Returns the pending reward and the claimable reward of the user `_user`. */ - function receiveReward(address valAddr) external payable; + function getRewards(address _user, address[] calldata _poolAddrList) + external + view + returns (uint256[] memory _pendings, uint256[] memory _claimables); /** - * @notice Delegators call this method to claim their pending rewards + * @dev Claims the reward of method caller. + * + * Emits the `RewardClaimed` event. + * */ - function claimReward(uint256 amount) external; + function claimRewards(address[] calldata _consensusAddrList) external returns (uint256 _amount); /** - * @notice Reduce amount stake from the validator. + * @dev Claims all pending rewards and delegates them to the consensus address. * * Requirements: - * - Only validators or Validator contract can call this function + * - The method caller is not the candidate admin. + * + * Emits the `RewardClaimed` event and the `Delegated` event. + * */ - function slash(address valAddr, uint256 amount) external; + function delegateRewards(address[] calldata _consensusAddrList, address _consensusAddrDst) + external + returns (uint256 _amount); } diff --git a/contracts/mocks/MockSwapStorage.sol b/contracts/mocks/MockSwapStorage.sol index e9970e394..51dd672db 100644 --- a/contracts/mocks/MockSwapStorage.sol +++ b/contracts/mocks/MockSwapStorage.sol @@ -6,12 +6,10 @@ import "../interfaces/IValidatorSet.sol"; import "../interfaces/IStaking.sol"; contract MockSwapStorage { - /// @dev Array of all validators. + /// @dev Array of all validators. IStaking.ValidatorCandidate[] public validatorSet; - function pushValidator(IStaking.ValidatorCandidate memory _incomingValidator) - external - { + function pushValidator(IStaking.ValidatorCandidate memory _incomingValidator) external { validatorSet.push(_incomingValidator); } @@ -19,7 +17,7 @@ contract MockSwapStorage { require(_index < validatorSet.length, "Access out-of-bound"); IStaking.ValidatorCandidate storage _validator = validatorSet[_index]; - _validator.stakingAddr = _incomingValidator.stakingAddr; + _validator.candidateAdmin = _incomingValidator.candidateAdmin; _validator.consensusAddr = _incomingValidator.consensusAddr; _validator.treasuryAddr = _incomingValidator.treasuryAddr; _validator.commissionRate = _incomingValidator.commissionRate; @@ -29,10 +27,10 @@ contract MockSwapStorage { } function swapValidators(uint256 _i, uint256 _j) external { - require(_i < validatorSet.length, "Access left element out-of-bound"); - require(_j < validatorSet.length, "Access right element out-of-bound"); - IStaking.ValidatorCandidate memory _tmp = validatorSet[_i]; + require(_i < validatorSet.length, "Access left element out-of-bound"); + require(_j < validatorSet.length, "Access right element out-of-bound"); + IStaking.ValidatorCandidate memory _tmp = validatorSet[_i]; _setValidatorAt(_i, validatorSet[_j]); _setValidatorAt(_j, _tmp); - } + } } diff --git a/contracts/staking/DPoStaking.sol b/contracts/staking/DPoStaking.sol new file mode 100644 index 000000000..6658d03b0 --- /dev/null +++ b/contracts/staking/DPoStaking.sol @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +// import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "../interfaces/IStaking.sol"; +import "../interfaces/IValidatorSet.sol"; +import "../libraries/Sorting.sol"; +import "./StakingManager.sol"; + +contract DPoStaking is IStaking, StakingManager, Initializable { + /// @dev The minimum threshold for being a validator candidate. + uint256 internal _minValidatorBalance; + /// @dev Maximum number of validator. + uint256 internal _maxValidatorCandidate; + /// @dev Governance admin contract address. + address internal _governanceAdminContract; /// TODO(Thor): add setter. + + uint256[] internal currentValidatorIndexes; // TODO(Bao): leave comments for this variable + IValidatorSet validatorSetContract; // TODO(Bao): leave comments for this variable + uint256 public numOfCabinets; // TODO(Bao): leave comments for this variable + /// @dev Configuration of number of blocks that validator has to wait before unstaking, counted from staking time + uint256 public unstakingOnHoldBlocksNum; // TODO(Bao): expose this fn in the interface + + /// @dev Mapping from consensus address => bitwise negation of validator index in `validatorCandidates`. + mapping(address => uint256) internal _candidateIndexes; + /// @dev The validator candidate array. + ValidatorCandidate[] public validatorCandidates; + + modifier onlyGovernanceAdminContract() { + require(msg.sender == _governanceAdminContract, "DPoStaking: unauthorized caller"); + _; + } + + modifier onlyValidatorContract() { + require(msg.sender == address(validatorSetContract), "DPoStaking: unauthorized caller"); + _; + } + + constructor() { + _disableInitializers(); + } + + /** + * @dev Initializes the contract storage. + */ + function initialize( + uint256 _unstakingOnHoldBlocksNum, + address __governanceAdminContract, + uint256 __maxValidatorCandidate, + uint256 __minValidatorBalance + ) external initializer { + unstakingOnHoldBlocksNum = _unstakingOnHoldBlocksNum; + _setGovernanceAdminContractAddress(__governanceAdminContract); + _setMaxValidatorCandidate(__maxValidatorCandidate); + _setMinValidatorBalance(__minValidatorBalance); + } + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR GOVERNANCE // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @inheritdoc IStaking + */ + function governanceAdminContract() public view override returns (address) { + return _governanceAdminContract; + } + + /** + * @inheritdoc IStaking + */ + function minValidatorBalance() public view override(IStaking, StakingManager) returns (uint256) { + return _minValidatorBalance; + } + + /** + * @inheritdoc IStaking + */ + function setMinValidatorBalance(uint256 _threshold) external onlyGovernanceAdminContract { + _setMinValidatorBalance(_threshold); + } + + /** + * @inheritdoc IStaking + */ + function maxValidatorCandidate() public view override(IStaking, StakingManager) returns (uint256) { + return _maxValidatorCandidate; + } + + /** + * @inheritdoc IStaking + */ + function setMaxValidatorCandidate(uint256 _threshold) external onlyGovernanceAdminContract { + _setMaxValidatorCandidate(_threshold); + } + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR VALIDATOR // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @inheritdoc IStaking + */ + function getValidatorCandidates() public view returns (ValidatorCandidate[] memory) { + return validatorCandidates; + } + + /** + * @inheritdoc IStaking + */ + function recordReward(address _consensusAddr, uint256 _reward) external onlyValidatorContract { + _recordReward(_consensusAddr, _reward); + } + + /** + * @inheritdoc IStaking + */ + function settleRewardPool(address _consensusAddr) external onlyValidatorContract { + _onPoolSettled(_consensusAddr); + } + + /** + * @inheritdoc IStaking + */ + function onValidatorSlashed(address _consensusAddr) external { + _onSlashed(_consensusAddr); + } + + /** + * @inheritdoc IStaking + */ + function deductStakingAmount(address _consensusAddr, uint256 _amount) external onlyValidatorContract { + ValidatorCandidate memory _candidate = _getCandidate(_consensusAddr); + _unstake(_consensusAddr, _candidate.candidateAdmin, _amount); + _undelegate(_consensusAddr, _candidate.candidateAdmin, _amount); + } + + /////////////////////////////////////////////////////////////////////////////////////// + // HELPER FUNCTIONS // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @inheritdoc StakingManager + */ + function _getCandidate(address _consensusAddr) + internal + view + override + returns (ValidatorCandidate storage _candidate) + { + uint256 _idx = ~_candidateIndexes[_consensusAddr]; + require(_idx > 0, "DPoStaking: query for nonexistent candidate"); + _candidate = validatorCandidates[_idx]; + } + + /** + * @inheritdoc StakingManager + */ + function _getCandidateIndexes(address _consensusAddr) internal view override returns (uint256) { + return _candidateIndexes[_consensusAddr]; + } + + /** + * @inheritdoc StakingManager + */ + function _getValidatorCandidateLength() internal view override returns (uint256) { + return validatorCandidates.length; + } + + /** + * @dev Sets the governance admin contract address. + * + * Emits the `GovernanceAdminContractUpdated` event. + * + */ + function _setGovernanceAdminContractAddress(address _newAddr) internal { + _governanceAdminContract = _newAddr; + emit GovernanceAdminContractUpdated(_newAddr); + } + + /** + * @dev Sets the minimum threshold for being a validator candidate. + * + * Emits the `MinValidatorBalanceUpdated` event. + * + */ + function _setMinValidatorBalance(uint256 _threshold) internal { + _minValidatorBalance = _threshold; + emit MinValidatorBalanceUpdated(_threshold); + } + + /** + * @dev Sets the maximum number of validator candidate. + * + * Requirements: + * - The method caller is governance admin. + * + * Emits the `MaxValidatorCandidateUpdated` event. + * + */ + function _setMaxValidatorCandidate(uint256 _threshold) internal { + _maxValidatorCandidate = _threshold; + emit MaxValidatorCandidateUpdated(_threshold); + } + + function _slashed(address _poolAddr, uint256 _period) internal view virtual override returns (bool) {} + + function _periodOf(uint256 _block) internal view virtual override returns (uint256) {} + + /** + * @inheritdoc StakingManager + */ + function _createValidatorCandidate( + address _consensusAddr, + address _candidateOwner, + address payable _treasuryAddr, + uint256 _commissionRate + ) internal virtual override returns (ValidatorCandidate memory) { + ValidatorCandidate storage _candidate = validatorCandidates.push(); + _candidate.consensusAddr = _consensusAddr; + _candidate.candidateAdmin = _candidateOwner; + _candidate.treasuryAddr = _treasuryAddr; + _candidate.commissionRate = _commissionRate; + return _candidate; + } +} diff --git a/contracts/staking/RewardCalculation.sol b/contracts/staking/RewardCalculation.sol index 331d032b7..6972557bf 100644 --- a/contracts/staking/RewardCalculation.sol +++ b/contracts/staking/RewardCalculation.sol @@ -6,7 +6,7 @@ pragma solidity ^0.8.9; * @title RewardCalculation contract * @dev This contract mainly contains to calculate reward for staking contract. * - * TODO: optimize gas cost when emitting SettledRewardUpdated and PendingRewardUpdated in the method `_claimReward`; + * TODO(Thor): optimize gas cost when emitting SettledRewardUpdated and PendingRewardUpdated in the method `_claimReward`; * */ abstract contract RewardCalculation { @@ -24,6 +24,8 @@ abstract contract RewardCalculation { ); /// @dev Emitted when the fields to calculate pending reward for the user is updated. event PendingRewardUpdated(address poolAddress, address user, uint256 debited, uint256 credited); + /// @dev Emitted when the user claimed their reward + event RewardClaimed(address poolAddress, address user, uint256 amount); struct PendingRewardFields { // Recorded reward amount. @@ -173,6 +175,7 @@ abstract contract RewardCalculation { */ function _claimReward(address _poolAddr, address _user) public returns (uint256 _amount) { _amount = getClaimableReward(_poolAddr, _user); + emit RewardClaimed(_poolAddr, _user, _amount); SettledPool memory _sPool = _settledPool[_poolAddr]; PendingRewardFields storage _reward = _pUserReward[_poolAddr][_user]; diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol new file mode 100644 index 000000000..976d7dcf9 --- /dev/null +++ b/contracts/staking/StakingManager.sol @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../interfaces/IStaking.sol"; +import "./RewardCalculation.sol"; + +abstract contract StakingManager is IStaking, RewardCalculation { + /// @dev Mapping from pool address => delegator address => delegated amount. + mapping(address => mapping(address => uint256)) internal _delegatedAmount; + + modifier noEmptyValue() { + require(msg.value > 0, "DPoStaking: query for empty value"); + _; + } + + modifier notCandidateOwner(address _consensusAddr) { + ValidatorCandidate memory _candidate = _getCandidate(_consensusAddr); + require(msg.sender != _candidate.candidateAdmin, "StakingManager: use unstake method instead"); + _; + } + + /** + * @inheritdoc RewardCalculation + */ + function balanceOf(address _poolAddr, address _user) public view override returns (uint256) { + return _delegatedAmount[_poolAddr][_user]; + } + + /** + * @inheritdoc RewardCalculation + */ + function totalBalance(address _poolAddr) public view override returns (uint256) { + ValidatorCandidate memory _candidate = _getCandidate(_poolAddr); + return _candidate.stakedAmount + _candidate.delegatedAmount; + } + + function minValidatorBalance() public view virtual returns (uint256); + + function maxValidatorCandidate() public view virtual returns (uint256); + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR VALIDATOR CANDIDATE // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @inheritdoc IStaking + */ + function proposeValidator( + address _consensusAddr, + address payable _treasuryAddr, + uint256 _commissionRate + ) external payable override returns (uint256 _candidateIdx) { + uint256 _amount = msg.value; + address _stakingAddr = msg.sender; + _candidateIdx = _proposeValidator(_consensusAddr, _treasuryAddr, _commissionRate, _amount, _stakingAddr); + _stake(_consensusAddr, _stakingAddr, _amount); + } + + /** + * @inheritdoc IStaking + */ + function stake(address _consensusAddr) external payable override noEmptyValue { + _stake(_consensusAddr, msg.sender, msg.value); + } + + /** + * @inheritdoc IStaking + */ + function unstake(address _consensusAddr, uint256 _amount) external override { + address _delegator = msg.sender; + _unstake(_consensusAddr, _delegator, _amount); + // TODO(Thor): replace by `call` and use reentrancy gruard + require(payable(msg.sender).send(_amount), "DPoStaking: could not transfer RON"); + } + + /** + * @inheritdoc IStaking + */ + function renounce(address consensusAddr) external { + revert("unimplemented"); + } + + /** + * @dev Proposes a candidate to become a valdiator. + * + * Requirements: + * - + * - The validator length is not exceeded the total validator threshold `_maxValidatorCandidate`. + * - The amount is larger than or equal to the minimum validator balance `_minValidatorBalance`. + * + * Emits the `ValidatorProposed` event. + * + * @return _candidateIdx The index of the candidate in the validator candidate list. + * + */ + function _proposeValidator( + address _consensusAddr, + address payable _treasuryAddr, + uint256 _commissionRate, + uint256 _amount, + address _candidateOwner + ) internal returns (uint256 _candidateIdx) { + uint256 _length = _getValidatorCandidateLength(); + require(_length < maxValidatorCandidate(), "DPoStaking: query for exceeded validator array length"); + require(_getCandidateIndexes(_consensusAddr) == 0, "DPoStaking: query for existed candidate"); + require(_amount > minValidatorBalance(), "DPoStaking: insuficient amount"); + require(_treasuryAddr.send(0), "DPoStaking: invalid treasury address"); // TODO(Thor): replace by `call` and use reentrancy gruard + + _candidateIdx = ~_length; + ValidatorCandidate memory _candidate = _createValidatorCandidate( + _consensusAddr, + _candidateOwner, + _treasuryAddr, + _commissionRate + ); + + emit ValidatorProposed(_consensusAddr, _candidateOwner, _amount, _candidate); + } + + /** + * @dev Stakes for the validator candidate. + * + * Requirements: + * - The user address is equal the candidate staking address. + * + * Emits the `Staked` event. + * + */ + function _stake( + address _poolAddr, + address _user, + uint256 _amount + ) internal { + ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); + require(_candidate.candidateAdmin == _user, "Staking: invalid staking address"); + + _candidate.stakedAmount += _amount; + emit Staked(_poolAddr, _amount); + + _delegate(_poolAddr, _user, _amount); + } + + /** + * @dev Withdraws the staked amount `_amount` for the validator candidate. + * + * Requirements: + * - The remain balance must be greater than the minimum validator candidate thresold `minValidatorBalance()`. + * + * Emits the `Unstaked` event. + * + */ + function _unstake( + address _poolAddr, + address _user, + uint256 _amount + ) internal { + ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); + require(_candidate.candidateAdmin == _user, "Staking: invalid staking address"); + require(_amount < _candidate.stakedAmount, "Staking: insufficient staked amount"); + + uint256 remainAmount = _candidate.stakedAmount - _amount; + require(remainAmount >= minValidatorBalance(), "Staking: invalid staked amount left"); + + _candidate.stakedAmount = _amount; + emit Unstaked(_poolAddr, _amount); + + _undelegate(_poolAddr, _user, _amount); + } + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR DELEGATOR // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @inheritdoc IStaking + */ + function delegate(address _consensusAddr) external payable noEmptyValue notCandidateOwner(_consensusAddr) { + _delegate(msg.sender, _consensusAddr, msg.value); + } + + /** + * @inheritdoc IStaking + */ + function undelegate(address _consensusAddr, uint256 _amount) external notCandidateOwner(_consensusAddr) { + address payable _delegator = payable(msg.sender); + _undelegate(_delegator, _consensusAddr, _amount); + // TODO(Thor): replace by `call` and use reentrancy gruard + require(_delegator.send(_amount), "DPoStaking: could not transfer RON"); + } + + /** + * @inheritdoc IStaking + */ + function redelegate( + address _consensusAddrSrc, + address _consensusAddrDst, + uint256 _amount + ) external notCandidateOwner(_consensusAddrDst) { + address _delegator = msg.sender; + _undelegate(_consensusAddrSrc, _delegator, _amount); + _delegate(_consensusAddrDst, _delegator, _amount); + } + + /** + * @inheritdoc IStaking + */ + function getRewards(address _user, address[] calldata _poolAddrList) + external + view + returns (uint256[] memory _pendings, uint256[] memory _claimables) + { + address _consensusAddr; + for (uint256 _i = 0; _i < _poolAddrList.length; _i++) { + _consensusAddr = _poolAddrList[_i]; + + uint256 _totalReward = getTotalReward(_consensusAddr, _user); + uint256 _claimableReward = getClaimableReward(_consensusAddr, _user); + _pendings[_i] = _totalReward - _claimableReward; + _claimables[_i] = _claimableReward; + } + } + + /** + * @inheritdoc IStaking + */ + function claimRewards(address[] calldata _consensusAddrList) external returns (uint256 _amount) { + _amount = _claimRewards(msg.sender, _consensusAddrList); + // TODO(Thor): replace by `call` and use reentrancy gruard + require(payable(msg.sender).send(_amount), "DPoStaking: could not transfer RON"); + } + + /** + * @inheritdoc IStaking + */ + function delegateRewards(address[] calldata _consensusAddrList, address _consensusAddrDst) + external + override + notCandidateOwner(_consensusAddrDst) + returns (uint256 _amount) + { + return _delegateRewards(msg.sender, _consensusAddrList, _consensusAddrDst); + } + + /** + * @dev Delegates from a validator address. + * + * Requirements: + * - The validator is an existed candidate. + * + * Emits the `Delegated` event. + * + * @notice This function does not verify the `msg.value` with the amount. + * + */ + function _delegate( + address _poolAddr, + address _user, + uint256 _amount + ) internal { + uint256 _newBalance = _delegatedAmount[_poolAddr][_user] + _amount; + _syncUserReward(_poolAddr, _user, _newBalance); + + ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); + _candidate.delegatedAmount += _amount; + _delegatedAmount[_poolAddr][_user] = _newBalance; + emit Delegated(_user, _poolAddr, _amount); + } + + /** + * @dev Claims rewards from the pools `_poolAddrList`. + * + *@notice This function does not transfer reward to user. + * + * TODO: Check whether pool addr is in the candidate list. or add test for this fn. + * + */ + function _claimRewards(address _user, address[] calldata _poolAddrList) internal returns (uint256 _amount) { + for (uint256 _i = 0; _i < _poolAddrList.length; _i++) { + _amount += _claimReward(_poolAddrList[_i], _user); + } + } + + /** + * @dev Claims all pending rewards and delegates them to the consensus address. + */ + function _delegateRewards( + address _user, + address[] calldata _poolAddrList, + address _poolAddrDst + ) internal returns (uint256 _amount) { + _amount = _claimRewards(_user, _poolAddrList); + _delegate(_user, _poolAddrDst, _amount); + } + + /** + * @dev Undelegates from a validator address. + * + * Requirements: + * - The validator is an existed candidate. + * - The delegated amount is larger than or equal to the undelegated amount. + * + * Emits the `Undelegated` event. + * + * @notice Consider transferring back the amount of RON after calling this function. + */ + function _undelegate( + address _poolAddr, + address _user, + uint256 _amount + ) internal { + require(_delegatedAmount[_poolAddr][_user] >= _amount, "Staking: insufficient amount to undelegate"); + + uint256 _newBalance = _delegatedAmount[_poolAddr][_user] - _amount; + _syncUserReward(_poolAddr, _user, _newBalance); + + ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); + _candidate.delegatedAmount -= _amount; + _delegatedAmount[_poolAddr][_user] = _amount; + emit Undelegated(_user, _poolAddr, _amount); + } + + /////////////////////////////////////////////////////////////////////////////////////// + // HELPER FUNCTIONS // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @dev Returns the validator candidate from storage form. + * + * Requirements: + * - The candidate is already existed. + * + */ + function _getCandidate(address _consensusAddr) internal view virtual returns (ValidatorCandidate storage _candidate); + + /** + * @dev Returns the bitwise negation of the candidate index in the response of function `getValidator Candidates()`. + * + * Requirements: + * - The candidate is already existed. + * + */ + function _getCandidateIndexes(address _consensusAddr) internal view virtual returns (uint256); + + /** + * @dev Returns the current validator length. + */ + function _getValidatorCandidateLength() internal view virtual returns (uint256); + + /** + * @dev Creates new validator candidate in the storage and returns its struct. + */ + function _createValidatorCandidate( + address _consensusAddr, + address _candidateOwner, + address payable _treasuryAddr, + uint256 _commissionRate + ) internal virtual returns (ValidatorCandidate memory); +} diff --git a/tests/sorting/MockSwapStorage.test.ts b/tests/sorting/MockSwapStorage.test.ts index 141f652f2..aaa790ca0 100644 --- a/tests/sorting/MockSwapStorage.test.ts +++ b/tests/sorting/MockSwapStorage.test.ts @@ -12,12 +12,12 @@ let candidates: ValidatorCandidateStruct[]; let admin: SignerWithAddress; const generateCandidate = ( - stakingAddr: string, + candidateAdmin: string, consensusAddr: string, treasuryAddr: string ): ValidatorCandidateStruct => { return { - stakingAddr: stakingAddr, + candidateAdmin: candidateAdmin, consensusAddr: consensusAddr, treasuryAddr: treasuryAddr, commissionRate: 0, diff --git a/tests/validators/ValidatorSetCore.test.ts b/tests/validators/ValidatorSetCore.test.ts index 343b45b87..19ded6b0e 100644 --- a/tests/validators/ValidatorSetCore.test.ts +++ b/tests/validators/ValidatorSetCore.test.ts @@ -15,12 +15,12 @@ let consensusAddrs: SignerWithAddress[]; let treasuryAddrs: SignerWithAddress[]; const generateCandidate = ( - stakingAddr: string, + candidateAdmin: string, consensusAddr: string, treasuryAddr: string ): ValidatorCandidateStruct => { return { - stakingAddr: stakingAddr, + candidateAdmin: candidateAdmin, consensusAddr: consensusAddr, treasuryAddr: treasuryAddr, commissionRate: 0, From 800eca7fe3f6424d270f834ce1c3cb2387504033 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Sun, 4 Sep 2022 06:21:00 +0700 Subject: [PATCH 038/190] [Staking] add test --- contracts/ValidatorSet.sol | 7 + contracts/interfaces/IStaking.sol | 4 +- contracts/interfaces/IValidatorSet.sol | 15 + contracts/mocks/{ => staking}/MockStaking.sol | 2 +- .../staking/MockValidatorSetForStaking.sol | 62 +++ contracts/staking/DPoStaking.sol | 60 +- contracts/staking/StakingManager.sol | 50 +- hardhat.config.ts | 6 +- src/deploy-tmp/dpostaking.ts | 32 ++ src/deploy-tmp/logic/dpostaking.ts | 17 + src/deploy-tmp/proxy-admin.ts | 15 + src/script/dpostaking.ts | 21 + test/staking/DPoStaking.test.ts | 524 ++++++++++++++++++ 13 files changed, 781 insertions(+), 34 deletions(-) rename contracts/mocks/{ => staking}/MockStaking.sol (98%) create mode 100644 contracts/mocks/staking/MockValidatorSetForStaking.sol create mode 100644 src/deploy-tmp/dpostaking.ts create mode 100644 src/deploy-tmp/logic/dpostaking.ts create mode 100644 src/deploy-tmp/proxy-admin.ts create mode 100644 src/script/dpostaking.ts create mode 100644 test/staking/DPoStaking.test.ts diff --git a/contracts/ValidatorSet.sol b/contracts/ValidatorSet.sol index d2486cc87..82a0e5d40 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/ValidatorSet.sol @@ -25,6 +25,9 @@ contract ValidatorSet is IValidatorSet, ValidatorSetCore { IStaking stakingContract; ISlashIndicator slashContract; + uint256 public numberOfEpochsInPeriod = 0; + uint256 public numberOfBlocksInEpoch = 0; + event ValidatorSetUpdated(); event ValidatorJailed(address indexed validator); /// @dev Event for each time the validators deposit mining reward @@ -183,4 +186,8 @@ contract ValidatorSet is IValidatorSet, ValidatorSetCore { } } } + + function periodOf(uint256 _block) external view override returns (uint256) { + return _block / numberOfEpochsInPeriod / numberOfBlocksInEpoch + 1; + } } diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 2035b566a..a944d581e 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -10,7 +10,8 @@ interface IStaking { address consensusAddr; /// @dev Address that receives mining reward of the validator address payable treasuryAddr; - /// @dev The percentile of reward that validators can be received, the rest goes to the delegators + /// @dev The percentile of reward that validators can be received, the rest goes to the delegators. + /// Values in range [0; 100_00] stands for 0-100% uint256 commissionRate; /// @dev The RON amount from the validator. uint256 stakedAmount; @@ -34,6 +35,7 @@ interface IStaking { event Delegated(address indexed delegator, address indexed validator, uint256 amount); event Undelegated(address indexed delegator, address indexed validator, uint256 amount); + event ValidatorContractUpdated(address); event GovernanceAdminContractUpdated(address); event MinValidatorBalanceUpdated(uint256 threshold); event MaxValidatorCandidateUpdated(uint256 threshold); diff --git a/contracts/interfaces/IValidatorSet.sol b/contracts/interfaces/IValidatorSet.sol index 957f6c086..7abf84dd9 100644 --- a/contracts/interfaces/IValidatorSet.sol +++ b/contracts/interfaces/IValidatorSet.sol @@ -92,4 +92,19 @@ interface IValidatorSet { * @notice Return last block height when the set of validators is updated */ function getLastUpdated() external view returns (uint256 height); + + /** + * @dev Returns the number of epochs in a period. + */ + function numberOfEpochsInPeriod() external view returns (uint256 _numberOfEpochs); + + /** + * @dev Returns the number of blocks in a epoch. + */ + function numberOfBlocksInEpoch() external view returns (uint256 _numberOfBlocks); + + /** + * @dev Returns the period index from the block number. + */ + function periodOf(uint256 _block) external view returns (uint256); } diff --git a/contracts/mocks/MockStaking.sol b/contracts/mocks/staking/MockStaking.sol similarity index 98% rename from contracts/mocks/MockStaking.sol rename to contracts/mocks/staking/MockStaking.sol index a99486e30..12e1bca6f 100644 --- a/contracts/mocks/MockStaking.sol +++ b/contracts/mocks/staking/MockStaking.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.9; -import "../staking/RewardCalculation.sol"; +import "../../staking/RewardCalculation.sol"; contract MockStaking is RewardCalculation { /// @dev Mapping from user => staking balance diff --git a/contracts/mocks/staking/MockValidatorSetForStaking.sol b/contracts/mocks/staking/MockValidatorSetForStaking.sol new file mode 100644 index 000000000..4a084adc9 --- /dev/null +++ b/contracts/mocks/staking/MockValidatorSetForStaking.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../../interfaces/IValidatorSet.sol"; +import "../../interfaces/IStaking.sol"; + +contract MockValidatorSetForStaking is IValidatorSet { + IStaking public stakingContract; + + uint256 public numberOfEpochsInPeriod; + uint256 public numberOfBlocksInEpoch; + + constructor( + IStaking _stakingContract, + uint256 _numberOfEpochsInPeriod, + uint256 _numberOfBlocksInEpoch + ) { + stakingContract = _stakingContract; + numberOfEpochsInPeriod = _numberOfEpochsInPeriod; + numberOfBlocksInEpoch = _numberOfBlocksInEpoch; + } + + function depositReward() external payable override { + stakingContract.recordReward(msg.sender, msg.value); + } + + function settledReward(address[] memory _validatorList) external { + for (uint _i = 0; _i < _validatorList.length; _i++) { + stakingContract.settleRewardPool(_validatorList[_i]); + } + } + + function slashMisdemeanor(address _validator) external override { + stakingContract.onValidatorSlashed(_validator); + } + + function slashFelony(address _validator) external override { + stakingContract.onValidatorSlashed(_validator); + stakingContract.deductStakingAmount(_validator, 1); + } + + function slashDoubleSign(address _validator) external override { + stakingContract.onValidatorSlashed(_validator); + } + + function periodOf(uint256 _block) external view override returns (uint256) { + return _block / numberOfBlocksInEpoch / numberOfEpochsInPeriod + 1; + } + + function updateValidators() external override returns (address[] memory) {} + + function getValidators() external view override returns (address[] memory) {} + + function isValidator(address validator) external view override returns (bool) {} + + function isWorkingValidator(address validator) external view override returns (bool) {} + + function isCurrentValidator(address validator) external view override returns (bool) {} + + function getLastUpdated() external view override returns (uint256 height) {} +} diff --git a/contracts/staking/DPoStaking.sol b/contracts/staking/DPoStaking.sol index 6658d03b0..23098f0e6 100644 --- a/contracts/staking/DPoStaking.sol +++ b/contracts/staking/DPoStaking.sol @@ -15,26 +15,29 @@ contract DPoStaking is IStaking, StakingManager, Initializable { /// @dev Maximum number of validator. uint256 internal _maxValidatorCandidate; /// @dev Governance admin contract address. - address internal _governanceAdminContract; /// TODO(Thor): add setter. + address internal _governanceAdminContract; // TODO(Thor): add setter. + /// @dev Validator contract address. + address internal _validatorContract; // Change type to address for testing purpose uint256[] internal currentValidatorIndexes; // TODO(Bao): leave comments for this variable - IValidatorSet validatorSetContract; // TODO(Bao): leave comments for this variable uint256 public numOfCabinets; // TODO(Bao): leave comments for this variable /// @dev Configuration of number of blocks that validator has to wait before unstaking, counted from staking time uint256 public unstakingOnHoldBlocksNum; // TODO(Bao): expose this fn in the interface /// @dev Mapping from consensus address => bitwise negation of validator index in `validatorCandidates`. - mapping(address => uint256) internal _candidateIndexes; + mapping(address => uint256) internal _candidateIndex; /// @dev The validator candidate array. ValidatorCandidate[] public validatorCandidates; + /// @dev Mapping from consensus address => period index => indicating the period is slashed or not. + mapping(address => mapping(uint256 => bool)) internal _periodSlashed; modifier onlyGovernanceAdminContract() { - require(msg.sender == _governanceAdminContract, "DPoStaking: unauthorized caller"); + require(msg.sender == _governanceAdminContract, "DPoStaking: method caller is not governance admin contract"); _; } modifier onlyValidatorContract() { - require(msg.sender == address(validatorSetContract), "DPoStaking: unauthorized caller"); + require(msg.sender == _validatorContract, "DPoStaking: method caller is not the validator contract"); _; } @@ -47,11 +50,13 @@ contract DPoStaking is IStaking, StakingManager, Initializable { */ function initialize( uint256 _unstakingOnHoldBlocksNum, + address __validatorContract, address __governanceAdminContract, uint256 __maxValidatorCandidate, uint256 __minValidatorBalance ) external initializer { unstakingOnHoldBlocksNum = _unstakingOnHoldBlocksNum; + _setValidatorContract(__validatorContract); _setGovernanceAdminContractAddress(__governanceAdminContract); _setMaxValidatorCandidate(__maxValidatorCandidate); _setMinValidatorBalance(__minValidatorBalance); @@ -111,7 +116,9 @@ contract DPoStaking is IStaking, StakingManager, Initializable { * @inheritdoc IStaking */ function recordReward(address _consensusAddr, uint256 _reward) external onlyValidatorContract { + console.log("*** =>>>>>>> recordReward", _consensusAddr, _reward); _recordReward(_consensusAddr, _reward); + console.log("*** =>>>>>>> recordReward", _pendingPool[_consensusAddr].accumulatedRps); } /** @@ -125,6 +132,8 @@ contract DPoStaking is IStaking, StakingManager, Initializable { * @inheritdoc IStaking */ function onValidatorSlashed(address _consensusAddr) external { + uint256 _period = _periodOf(block.number); + _periodSlashed[_consensusAddr][_period] = true; _onSlashed(_consensusAddr); } @@ -150,16 +159,24 @@ contract DPoStaking is IStaking, StakingManager, Initializable { override returns (ValidatorCandidate storage _candidate) { - uint256 _idx = ~_candidateIndexes[_consensusAddr]; + uint256 _idx = _candidateIndex[_consensusAddr]; + console.log("_getCandidate:", _consensusAddr, _idx, ~_idx); require(_idx > 0, "DPoStaking: query for nonexistent candidate"); - _candidate = validatorCandidates[_idx]; + _candidate = validatorCandidates[~_idx]; } /** * @inheritdoc StakingManager */ - function _getCandidateIndexes(address _consensusAddr) internal view override returns (uint256) { - return _candidateIndexes[_consensusAddr]; + function _getCandidateIndex(address _consensusAddr) internal view override returns (uint256) { + return _candidateIndex[_consensusAddr]; + } + + /** + * @inheritdoc StakingManager + */ + function _setCandidateIndex(address _consensusAddr, uint256 _candidateIdx) internal override { + _candidateIndex[_consensusAddr] = _candidateIdx; } /** @@ -180,6 +197,17 @@ contract DPoStaking is IStaking, StakingManager, Initializable { emit GovernanceAdminContractUpdated(_newAddr); } + /** + * @dev Sets the governance admin contract address. + * + * Emits the `ValidatorContractUpdated` event. + * + */ + function _setValidatorContract(address _newValidatorContract) internal { + _validatorContract = _newValidatorContract; + emit ValidatorContractUpdated(_newValidatorContract); + } + /** * @dev Sets the minimum threshold for being a validator candidate. * @@ -205,9 +233,19 @@ contract DPoStaking is IStaking, StakingManager, Initializable { emit MaxValidatorCandidateUpdated(_threshold); } - function _slashed(address _poolAddr, uint256 _period) internal view virtual override returns (bool) {} + /** + * @inheritdoc RewardCalculation + */ + function _slashed(address _poolAddr, uint256 _period) internal view virtual override returns (bool) { + return _periodSlashed[_poolAddr][_period]; + } - function _periodOf(uint256 _block) internal view virtual override returns (uint256) {} + /** + * @inheritdoc RewardCalculation + */ + function _periodOf(uint256 _block) internal view virtual override returns (uint256) { + return IValidatorSet(_validatorContract).periodOf(_block); + } /** * @inheritdoc StakingManager diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 976d7dcf9..3ccbbced0 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.9; +import "hardhat/console.sol"; import "../interfaces/IStaking.sol"; import "./RewardCalculation.sol"; @@ -10,7 +11,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { mapping(address => mapping(address => uint256)) internal _delegatedAmount; modifier noEmptyValue() { - require(msg.value > 0, "DPoStaking: query for empty value"); + require(msg.value > 0, "StakingManager: query for empty value"); _; } @@ -54,7 +55,10 @@ abstract contract StakingManager is IStaking, RewardCalculation { uint256 _amount = msg.value; address _stakingAddr = msg.sender; _candidateIdx = _proposeValidator(_consensusAddr, _treasuryAddr, _commissionRate, _amount, _stakingAddr); + console.log("_proposeValidator: done"); + console.log("_stake: bf", _candidateIdx); _stake(_consensusAddr, _stakingAddr, _amount); + console.log("_stake: af"); } /** @@ -71,7 +75,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { address _delegator = msg.sender; _unstake(_consensusAddr, _delegator, _amount); // TODO(Thor): replace by `call` and use reentrancy gruard - require(payable(msg.sender).send(_amount), "DPoStaking: could not transfer RON"); + require(payable(msg.sender).send(_amount), "StakingManager: could not transfer RON"); } /** @@ -91,7 +95,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { * * Emits the `ValidatorProposed` event. * - * @return _candidateIdx The index of the candidate in the validator candidate list. + * @return _candidateIdx The bitwise negative of candidate index. * */ function _proposeValidator( @@ -102,12 +106,14 @@ abstract contract StakingManager is IStaking, RewardCalculation { address _candidateOwner ) internal returns (uint256 _candidateIdx) { uint256 _length = _getValidatorCandidateLength(); - require(_length < maxValidatorCandidate(), "DPoStaking: query for exceeded validator array length"); - require(_getCandidateIndexes(_consensusAddr) == 0, "DPoStaking: query for existed candidate"); - require(_amount > minValidatorBalance(), "DPoStaking: insuficient amount"); - require(_treasuryAddr.send(0), "DPoStaking: invalid treasury address"); // TODO(Thor): replace by `call` and use reentrancy gruard + require(_length < maxValidatorCandidate(), "StakingManager: query for exceeded validator array length"); + require(_getCandidateIndex(_consensusAddr) == 0, "StakingManager: query for existed candidate"); + require(_amount >= minValidatorBalance(), "StakingManager: insufficient amount"); + // TODO(Thor): replace by `call` and use reentrancy gruard + require(_treasuryAddr.send(0), "StakingManager: invalid treasury address"); _candidateIdx = ~_length; + _setCandidateIndex(_consensusAddr, _candidateIdx); ValidatorCandidate memory _candidate = _createValidatorCandidate( _consensusAddr, _candidateOwner, @@ -133,7 +139,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { uint256 _amount ) internal { ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); - require(_candidate.candidateAdmin == _user, "Staking: invalid staking address"); + require(_candidate.candidateAdmin == _user, "StakingManager: invalid staking address"); _candidate.stakedAmount += _amount; emit Staked(_poolAddr, _amount); @@ -156,11 +162,11 @@ abstract contract StakingManager is IStaking, RewardCalculation { uint256 _amount ) internal { ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); - require(_candidate.candidateAdmin == _user, "Staking: invalid staking address"); - require(_amount < _candidate.stakedAmount, "Staking: insufficient staked amount"); + require(_candidate.candidateAdmin == _user, "StakingManager: invalid staking address"); + require(_amount < _candidate.stakedAmount, "StakingManager: insufficient staked amount"); uint256 remainAmount = _candidate.stakedAmount - _amount; - require(remainAmount >= minValidatorBalance(), "Staking: invalid staked amount left"); + require(remainAmount >= minValidatorBalance(), "StakingManager: invalid staked amount left"); _candidate.stakedAmount = _amount; emit Unstaked(_poolAddr, _amount); @@ -176,7 +182,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { * @inheritdoc IStaking */ function delegate(address _consensusAddr) external payable noEmptyValue notCandidateOwner(_consensusAddr) { - _delegate(msg.sender, _consensusAddr, msg.value); + _delegate(_consensusAddr, msg.sender, msg.value); } /** @@ -184,9 +190,9 @@ abstract contract StakingManager is IStaking, RewardCalculation { */ function undelegate(address _consensusAddr, uint256 _amount) external notCandidateOwner(_consensusAddr) { address payable _delegator = payable(msg.sender); - _undelegate(_delegator, _consensusAddr, _amount); + _undelegate(_consensusAddr, _delegator, _amount); // TODO(Thor): replace by `call` and use reentrancy gruard - require(_delegator.send(_amount), "DPoStaking: could not transfer RON"); + require(_delegator.send(_amount), "StakingManager: could not transfer RON"); } /** @@ -227,7 +233,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { function claimRewards(address[] calldata _consensusAddrList) external returns (uint256 _amount) { _amount = _claimRewards(msg.sender, _consensusAddrList); // TODO(Thor): replace by `call` and use reentrancy gruard - require(payable(msg.sender).send(_amount), "DPoStaking: could not transfer RON"); + require(payable(msg.sender).send(_amount), "StakingManager: could not transfer RON"); } /** @@ -259,6 +265,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { uint256 _amount ) internal { uint256 _newBalance = _delegatedAmount[_poolAddr][_user] + _amount; + console.log("_delegate", _poolAddr, _user, _newBalance); _syncUserReward(_poolAddr, _user, _newBalance); ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); @@ -290,7 +297,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { address _poolAddrDst ) internal returns (uint256 _amount) { _amount = _claimRewards(_user, _poolAddrList); - _delegate(_user, _poolAddrDst, _amount); + _delegate(_poolAddrDst, _user, _amount); } /** @@ -309,7 +316,9 @@ abstract contract StakingManager is IStaking, RewardCalculation { address _user, uint256 _amount ) internal { - require(_delegatedAmount[_poolAddr][_user] >= _amount, "Staking: insufficient amount to undelegate"); + console.log("_undelegate", _poolAddr, _user); + console.log("_undelegate", _delegatedAmount[_poolAddr][_user], _amount); + require(_delegatedAmount[_poolAddr][_user] >= _amount, "StakingManager: insufficient amount to undelegate"); uint256 _newBalance = _delegatedAmount[_poolAddr][_user] - _amount; _syncUserReward(_poolAddr, _user, _newBalance); @@ -340,7 +349,12 @@ abstract contract StakingManager is IStaking, RewardCalculation { * - The candidate is already existed. * */ - function _getCandidateIndexes(address _consensusAddr) internal view virtual returns (uint256); + function _getCandidateIndex(address _consensusAddr) internal view virtual returns (uint256); + + /** + * @dev Sets the candidate index. + */ + function _setCandidateIndex(address _consensusAddr, uint256 _candidateIdx) internal virtual; /** * @dev Returns the current validator length. diff --git a/hardhat.config.ts b/hardhat.config.ts index 492282839..7e132c662 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -63,7 +63,7 @@ const config: HardhatUserConfig = { hardhat: { accounts: { mnemonic: DEFAULT_MNEMONIC, - count: 100 + count: 10, }, }, 'ronin-testnet': testnet, @@ -71,8 +71,8 @@ const config: HardhatUserConfig = { }, gasReporter: { enabled: REPORT_GAS ? true : false, - showTimeSpent: true - } + showTimeSpent: true, + }, }; export default config; diff --git a/src/deploy-tmp/dpostaking.ts b/src/deploy-tmp/dpostaking.ts new file mode 100644 index 000000000..e7b2fea2f --- /dev/null +++ b/src/deploy-tmp/dpostaking.ts @@ -0,0 +1,32 @@ +import { network } from 'hardhat'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { stakingConfig } from '../script/dpostaking'; +import { DPoStaking__factory } from '../types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + const proxyAdmin = await deployments.get('ProxyAdmin'); + const logicContract = await deployments.get('DPoStakingLogic'); + + const data = new DPoStaking__factory().interface.encodeFunctionData('initialize', [ + stakingConfig[network.name]!.unstakingOnHoldBlocksNum, + stakingConfig[network.name]!.validatorContract, + stakingConfig[network.name]!.governanceAdminContract, + stakingConfig[network.name]!.maxValidatorCandidate, + stakingConfig[network.name]!.minValidatorBalance, + ]); + + await deploy('DPoStakingProxy', { + contract: 'TransparentUpgradeableProxy', + from: deployer, + log: true, + args: [logicContract.address, proxyAdmin.address, data], + }); +}; + +deploy.tags = ['DPoStakingProxy']; +deploy.dependencies = ['ProxyAdmin', 'DPoStakingLogic']; + +export default deploy; diff --git a/src/deploy-tmp/logic/dpostaking.ts b/src/deploy-tmp/logic/dpostaking.ts new file mode 100644 index 000000000..945bc9efa --- /dev/null +++ b/src/deploy-tmp/logic/dpostaking.ts @@ -0,0 +1,17 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + await deploy('DPoStakingLogic', { + contract: 'DPoStaking', + from: deployer, + log: true, + }); +}; + +deploy.tags = ['DPoStakingLogic']; +deploy.dependencies = ['ProxyAdmin']; + +export default deploy; diff --git a/src/deploy-tmp/proxy-admin.ts b/src/deploy-tmp/proxy-admin.ts new file mode 100644 index 000000000..781cf2f9e --- /dev/null +++ b/src/deploy-tmp/proxy-admin.ts @@ -0,0 +1,15 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + await deploy('ProxyAdmin', { + from: deployer, + log: true, + }); +}; + +deploy.tags = ['ProxyAdmin']; + +export default deploy; diff --git a/src/script/dpostaking.ts b/src/script/dpostaking.ts new file mode 100644 index 000000000..c0e2e7775 --- /dev/null +++ b/src/script/dpostaking.ts @@ -0,0 +1,21 @@ +import { BigNumberish } from 'ethers'; +import { LiteralNetwork, Network } from '../addresses'; + +interface DPoStakingConf { + [network: LiteralNetwork]: + | { + unstakingOnHoldBlocksNum: BigNumberish; + validatorContract: BigNumberish; + governanceAdminContract: BigNumberish; + maxValidatorCandidate: BigNumberish; + minValidatorBalance: BigNumberish; + } + | undefined; +} + +// TODO: update config for testnet & mainnet +export const stakingConfig: DPoStakingConf = { + [Network.Hardhat]: undefined, + [Network.Testnet]: undefined, + [Network.Mainnet]: undefined, +}; diff --git a/test/staking/DPoStaking.test.ts b/test/staking/DPoStaking.test.ts new file mode 100644 index 000000000..a9c57d1a6 --- /dev/null +++ b/test/staking/DPoStaking.test.ts @@ -0,0 +1,524 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { expect } from 'chai'; +import { BigNumber, BigNumberish } from 'ethers'; +import { ethers, network } from 'hardhat'; + +import { + DPoStaking, + DPoStaking__factory, + MockStaking__factory, + TransparentUpgradeableProxy__factory, +} from '../../src/types'; +import { MockValidatorSetForStaking__factory } from '../../src/types/factories/MockValidatorSetForStaking__factory'; +import { MockValidatorSetForStaking } from '../../src/types/MockValidatorSetForStaking'; + +const EPS = 1; + +let poolAddr: string = ethers.constants.AddressZero; +let otherPoolAddr: string = ethers.constants.AddressZero; +let deployer: SignerWithAddress; +let proxyAdmin: SignerWithAddress; +let userA: SignerWithAddress; +let userB: SignerWithAddress; +let validatorContract: MockValidatorSetForStaking; +let stakingContract: DPoStaking; +let validatorCandidates: SignerWithAddress[]; + +const local = { + accumulatedRewardForA: BigNumber.from(0), + accumulatedRewardForB: BigNumber.from(0), + claimableRewardForA: BigNumber.from(0), + claimableRewardForB: BigNumber.from(0), + recordReward: async function (reward: BigNumberish) { + const totalStaked = await stakingContract.totalBalance(poolAddr); + const stakingAmountA = await stakingContract.balanceOf(poolAddr, userA.address); + const stakingAmountB = await stakingContract.balanceOf(poolAddr, userB.address); + this.accumulatedRewardForA = this.accumulatedRewardForA.add( + BigNumber.from(reward).mul(stakingAmountA).div(totalStaked) + ); + this.accumulatedRewardForB = this.accumulatedRewardForB.add( + BigNumber.from(reward).mul(stakingAmountB).div(totalStaked) + ); + }, + commitRewardPool: function () { + this.claimableRewardForA = this.accumulatedRewardForA; + this.claimableRewardForB = this.accumulatedRewardForB; + }, + slash: function () { + this.accumulatedRewardForA = this.claimableRewardForA; + this.accumulatedRewardForB = this.claimableRewardForB; + }, + reset: function () { + this.claimableRewardForA = BigNumber.from(0); + this.claimableRewardForB = BigNumber.from(0); + this.accumulatedRewardForA = BigNumber.from(0); + this.accumulatedRewardForB = BigNumber.from(0); + }, + claimRewardForA: function () { + this.accumulatedRewardForA = this.accumulatedRewardForA.sub(this.claimableRewardForA); + this.claimableRewardForA = BigNumber.from(0); + }, + claimRewardForB: function () { + this.accumulatedRewardForB = this.accumulatedRewardForB.sub(this.claimableRewardForB); + this.claimableRewardForB = BigNumber.from(0); + }, +}; + +const expectLocalCalculationRight = async () => { + console.log('expectLocalCalculationRight', poolAddr); + + { + const userReward = await stakingContract.getTotalReward(poolAddr, userA.address); + expect( + userReward.sub(local.accumulatedRewardForA).abs().lte(EPS), + `invalid user reward for A expected=${local.accumulatedRewardForA.toString()} actual=${userReward}` + ).to.be.true; + const claimableReward = await stakingContract.getClaimableReward(poolAddr, userA.address); + expect( + claimableReward.sub(local.claimableRewardForA).abs().lte(EPS), + `invalid claimable reward for A expected=${local.claimableRewardForA.toString()} actual=${claimableReward}` + ).to.be.true; + } + { + const userReward = await stakingContract.getTotalReward(poolAddr, userB.address); + expect( + userReward.sub(local.accumulatedRewardForB).abs().lte(EPS), + `invalid user reward for B expected=${local.accumulatedRewardForB.toString()} actual=${userReward}` + ).to.be.true; + const claimableReward = await stakingContract.getClaimableReward(poolAddr, userB.address); + expect( + claimableReward.sub(local.claimableRewardForB).abs().lte(EPS), + `invalid claimable reward for B expected=${local.claimableRewardForB.toString()} actual=${claimableReward}` + ).to.be.true; + } +}; + +const minValidatorBalance = BigNumber.from(10).pow(18); + +describe('DPoStaking test', () => { + before(async () => { + [deployer, proxyAdmin, userA, userB, ...validatorCandidates] = await ethers.getSigners(); + const nonce = await deployer.getTransactionCount(); + const proxyContractAddress = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); + validatorContract = await new MockValidatorSetForStaking__factory(deployer).deploy(proxyContractAddress, 10, 2); + const logicContract = await new DPoStaking__factory(deployer).deploy(); + const proxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + logicContract.address, + proxyAdmin.address, + logicContract.interface.encodeFunctionData('initialize', [ + 28800, + validatorContract.address, + ethers.constants.AddressZero, + 50, + minValidatorBalance, + ]) + ); + stakingContract = DPoStaking__factory.connect(proxyContract.address, deployer); + expect(proxyContractAddress.toLowerCase()).eq(proxyContract.address.toLowerCase()); + poolAddr = validatorCandidates[0].address; + otherPoolAddr = validatorCandidates[1].address; + }); + + it('Should not be able to propose validator with insufficient amount', async () => { + await expect(stakingContract.proposeValidator(userA.address, userA.address, 1)).revertedWith( + 'StakingManager: insufficient amount' + ); + }); + + it('Should be able to propose validator with sufficient amount', async () => { + for (let i = 0; i < validatorCandidates.length; i++) { + const candidate = validatorCandidates[i]; + await stakingContract.connect(candidate).proposeValidator( + candidate.address, + candidate.address, + 1, // 0.01% + { value: minValidatorBalance } + ); + } + await network.provider.send('evm_setAutomine', [false]); + }); + + it('Should work properly with staking actions occurring sequentially for a normal period', async () => { + console.log({ poolAddr, otherPoolAddr }); + console.log([userA.address, userB.address]); + + // TODO: subtract commission rate in contract + console.log('0'); + + await stakingContract.connect(userA).delegate(poolAddr, { value: 100 }); + console.log('1'); + await stakingContract.connect(userB).delegate(poolAddr, { value: 100 }); + console.log('2'); + await stakingContract.connect(userA).delegate(otherPoolAddr, { value: 100 }); + console.log('3'); + await network.provider.send('evm_mine'); + console.log('===========>a', [ + await stakingContract.balanceOf(poolAddr, userA.address), + await stakingContract.totalBalance(poolAddr), + ]); + + console.log( + await ethers.provider.getBlockNumber(), + await validatorContract.periodOf(await ethers.provider.getBlockNumber()) + ); + + await validatorContract.connect(validatorCandidates[0]).depositReward({ value: 1000 }); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + console.log( + 0, + await ethers.provider.getBlockNumber(), + await validatorContract.periodOf(await ethers.provider.getBlockNumber()) + ); + + await validatorContract.connect(validatorCandidates[1]).depositReward({ value: 1000 }); + await network.provider.send('evm_mine'); + console.log( + 1, + await ethers.provider.getBlockNumber(), + await validatorContract.periodOf(await ethers.provider.getBlockNumber()) + ); + await network.provider.send('evm_mine'); + console.log( + 2, + await ethers.provider.getBlockNumber(), + await validatorContract.periodOf(await ethers.provider.getBlockNumber()) + ); + await network.provider.send('evm_mine'); + console.log( + 3, + await ethers.provider.getBlockNumber(), + await validatorContract.periodOf(await ethers.provider.getBlockNumber()) + ); + await local.recordReward(1000); + await expectLocalCalculationRight(); + console.log( + 4, + await ethers.provider.getBlockNumber(), + await validatorContract.periodOf(await ethers.provider.getBlockNumber()) + ); + + await stakingContract.connect(userA).delegate(poolAddr, { value: 100 }); + await network.provider.send('evm_mine'); + console.log( + 5, + await ethers.provider.getBlockNumber(), + await validatorContract.periodOf(await ethers.provider.getBlockNumber()) + ); + await expectLocalCalculationRight(); + + await validatorContract.connect(validatorCandidates[0]).depositReward({ value: 1000 }); + await network.provider.send('evm_mine'); + console.log( + 6, + await ethers.provider.getBlockNumber(), + await validatorContract.periodOf(await ethers.provider.getBlockNumber()) + ); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await stakingContract.connect(userA).undelegate(poolAddr, 200); + await validatorContract.connect(validatorCandidates[0]).depositReward({ value: 1000 }); + await network.provider.send('evm_mine'); + console.log( + 7, + await ethers.provider.getBlockNumber(), + await validatorContract.periodOf(await ethers.provider.getBlockNumber()) + ); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + // await stakingContract.stake(userA.address, 200); + await network.provider.send('evm_mine'); + console.log( + 8, + await ethers.provider.getBlockNumber(), + await validatorContract.periodOf(await ethers.provider.getBlockNumber()) + ); + await local.recordReward(0); + await expectLocalCalculationRight(); + + await validatorContract.settledReward([poolAddr, otherPoolAddr]); + // await stakingContract.endPeriod(); + await network.provider.send('evm_mine'); + console.log( + 9, + await ethers.provider.getBlockNumber(), + await validatorContract.periodOf(await ethers.provider.getBlockNumber()) + ); + console.log(local); + + local.commitRewardPool(); // ?? WHY: still right + console.log(local); + await expectLocalCalculationRight(); + + await network.provider.send('evm_mine'); + console.log( + 9, + await ethers.provider.getBlockNumber(), + await validatorContract.periodOf(await ethers.provider.getBlockNumber()) + ); + }); + + // it('Should work properly with staking actions occurring sequentially for a slashed period', async () => { + // await stakingContract.stake(userA.address, 100); + // await network.provider.send('evm_mine'); + // await expectLocalCalculationRight(); + // await local.recordReward(0); + + // await stakingContract.recordReward(1000); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // await expectLocalCalculationRight(); + + // await stakingContract.recordReward(1000); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // await expectLocalCalculationRight(); + + // await stakingContract.stake(userA.address, 300); + // await stakingContract.recordReward(1000); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // await expectLocalCalculationRight(); + + // await stakingContract.slash(); + // await network.provider.send('evm_mine'); + // local.slash(); + // await expectLocalCalculationRight(); + + // await stakingContract.recordReward(0); + // await network.provider.send('evm_mine'); + // await local.recordReward(0); + // await expectLocalCalculationRight(); + + // await network.provider.send('evm_mine'); + // await network.provider.send('evm_mine'); + // await network.provider.send('evm_mine'); + // await network.provider.send('evm_mine'); + + // await stakingContract.unstake(userA.address, 300); + // await network.provider.send('evm_mine'); + // await stakingContract.unstake(userA.address, 100); + // await network.provider.send('evm_mine'); + // await expectLocalCalculationRight(); + + // await stakingContract.commitRewardPool(); + // await stakingContract.endPeriod(); + // await network.provider.send('evm_mine'); + // await expectLocalCalculationRight(); + // }); + + // it('Should work properly with staking actions occurring sequentially for a slashed period again', async () => { + // await stakingContract.stake(userA.address, 100); + // await network.provider.send('evm_mine'); + // await local.recordReward(0); + // await expectLocalCalculationRight(); + + // await stakingContract.claimReward(userA.address); + // await stakingContract.recordReward(1000); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // local.claimRewardForA(); + // await expectLocalCalculationRight(); + + // await stakingContract.recordReward(1000); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // await expectLocalCalculationRight(); + + // await stakingContract.stake(userA.address, 300); + // await stakingContract.recordReward(1000); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // await expectLocalCalculationRight(); + + // await stakingContract.slash(); + // await network.provider.send('evm_mine'); + // local.slash(); + // await expectLocalCalculationRight(); + + // await stakingContract.recordReward(0); + // await network.provider.send('evm_mine'); + // await local.recordReward(0); + // await expectLocalCalculationRight(); + + // await network.provider.send('evm_mine'); + // await network.provider.send('evm_mine'); + // await network.provider.send('evm_mine'); + // await network.provider.send('evm_mine'); + + // await stakingContract.unstake(userA.address, 300); + // await network.provider.send('evm_mine'); + // await stakingContract.unstake(userA.address, 100); + // await network.provider.send('evm_mine'); + // await expectLocalCalculationRight(); + + // await stakingContract.claimReward(userB.address); + // await stakingContract.commitRewardPool(); + // await stakingContract.endPeriod(); + // await network.provider.send('evm_mine'); + // local.claimRewardForB(); + // await expectLocalCalculationRight(); + // }); + + // it('Should be able to calculate right reward after claiming', async () => { + // await stakingContract.recordReward(1000); + // await stakingContract.commitRewardPool(); + // await stakingContract.endPeriod(); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // local.commitRewardPool(); + // await expectLocalCalculationRight(); + + // await stakingContract.claimReward(userA.address); + // await stakingContract.recordReward(1000); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // local.claimRewardForA(); + // await expectLocalCalculationRight(); + + // await stakingContract.claimReward(userA.address); + // await network.provider.send('evm_mine'); + // local.claimRewardForA(); + // await expectLocalCalculationRight(); + + // await stakingContract.claimReward(userB.address); + // await stakingContract.commitRewardPool(); + // await stakingContract.endPeriod(); + // await network.provider.send('evm_mine'); + // local.claimRewardForB(); + // local.commitRewardPool(); + // await expectLocalCalculationRight(); + // }); + + // it('Should work properly with staking actions from multi-users occurring in the same block', async () => { + // await stakingContract.stake(userA.address, 100); + // await network.provider.send('evm_mine'); + + // await stakingContract.stake(userA.address, 300); + // await stakingContract.recordReward(1000); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // await expectLocalCalculationRight(); + + // await stakingContract.stake(userB.address, 200); + // await stakingContract.recordReward(1000); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // await expectLocalCalculationRight(); + + // await stakingContract.recordReward(1000); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // await expectLocalCalculationRight(); + + // await stakingContract.recordReward(1000); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // await expectLocalCalculationRight(); + + // await stakingContract.unstake(userB.address, 200); + // await stakingContract.unstake(userA.address, 400); + // await stakingContract.recordReward(1000); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // await expectLocalCalculationRight(); + + // await stakingContract.claimReward(userA.address); + // await stakingContract.claimReward(userB.address); + // await network.provider.send('evm_mine'); + // local.claimRewardForA(); + // local.claimRewardForB(); + // await expectLocalCalculationRight(); + + // await stakingContract.unstake(userA.address, 200); + // await stakingContract.commitRewardPool(); + // await stakingContract.endPeriod(); + // await network.provider.send('evm_mine'); + // local.commitRewardPool(); + // await expectLocalCalculationRight(); + + // await stakingContract.claimReward(userA.address); + // await stakingContract.claimReward(userB.address); + // await network.provider.send('evm_mine'); + // local.claimRewardForA(); + // local.claimRewardForB(); + // await expectLocalCalculationRight(); + // }); + + // it('Should work properly with staking actions occurring in the same block', async () => { + // await stakingContract.stake(userA.address, 100); + // await stakingContract.unstake(userA.address, 100); + // await stakingContract.stake(userA.address, 100); + // await stakingContract.unstake(userA.address, 100); + // await stakingContract.stake(userB.address, 200); + // await stakingContract.unstake(userB.address, 200); + // await stakingContract.stake(userB.address, 200); + // await stakingContract.unstake(userB.address, 200); + // await stakingContract.stake(userB.address, 200); + // await stakingContract.stake(userA.address, 100); + // await stakingContract.unstake(userA.address, 100); + // await stakingContract.unstake(userB.address, 200); + // await stakingContract.stake(userB.address, 200); + // await stakingContract.unstake(userA.address, 100); + // await stakingContract.stake(userA.address, 100); + // await stakingContract.unstake(userB.address, 200); + // await stakingContract.recordReward(1000); + // await stakingContract.commitRewardPool(); + // await stakingContract.endPeriod(); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // local.commitRewardPool(); + // await expectLocalCalculationRight(); + + // await stakingContract.recordReward(1000); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // await expectLocalCalculationRight(); + + // await stakingContract.slash(); + // await network.provider.send('evm_mine'); + // local.slash(); + // await expectLocalCalculationRight(); + + // await stakingContract.commitRewardPool(); + // await stakingContract.endPeriod(); + // await network.provider.send('evm_mine'); + // await expectLocalCalculationRight(); + + // await stakingContract.recordReward(1000); + // await stakingContract.commitRewardPool(); + // await stakingContract.endPeriod(); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // local.commitRewardPool(); + // await expectLocalCalculationRight(); + + // await stakingContract.recordReward(1000); + // await stakingContract.commitRewardPool(); + // await stakingContract.endPeriod(); + // await network.provider.send('evm_mine'); + // await local.recordReward(1000); + // local.commitRewardPool(); + // await expectLocalCalculationRight(); + + // await stakingContract.claimReward(userA.address); + // await stakingContract.claimReward(userB.address); + // await stakingContract.claimReward(userA.address); + // await stakingContract.claimReward(userB.address); + // await stakingContract.claimReward(userA.address); + // await stakingContract.claimReward(userB.address); + // await stakingContract.claimReward(userA.address); + // await stakingContract.claimReward(userA.address); + // await stakingContract.claimReward(userA.address); + // await stakingContract.claimReward(userB.address); + // await stakingContract.claimReward(userB.address); + // await stakingContract.claimReward(userB.address); + // await network.provider.send('evm_mine'); + // local.claimRewardForA(); + // local.claimRewardForB(); + // await expectLocalCalculationRight(); + // }); +}); From 79a3db9a697a7bf40433e07f58005a56b1a1fb7c Mon Sep 17 00:00:00 2001 From: nxqbao Date: Fri, 2 Sep 2022 15:27:06 +0700 Subject: [PATCH 039/190] [Staking] Fix proposeValidator function --- contracts/Staking.sol | 294 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 contracts/Staking.sol diff --git a/contracts/Staking.sol b/contracts/Staking.sol new file mode 100644 index 000000000..d81b56561 --- /dev/null +++ b/contracts/Staking.sol @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "./interfaces/IStaking.sol"; +import "./interfaces/IValidatorSet.sol"; +import "./libraries/Sorting.sol"; + +contract Staking is IStaking, Initializable { + /// TODO: expose fn to get validator info by validator address. + /// @dev Mapping from consensus address => validator index in `validatorCandidates`. + mapping(address => uint256) internal _validatorIndexes; + /// TODO: expose fn returns the whole validator arry. + /// @dev Validator array. + ValidatorCandidate[] public validatorCandidates; + + /// @dev Mapping from delegator address => consensus address => delegated amount. + mapping(address => mapping(address => uint256)) delegatedAmount; + + // TODO: expose this fn in the interface + /// @dev Configuration of maximum number of validator + uint256 public totalValidatorThreshold; + + // TODO: expose this fn in the interface + /// @dev Configuration of number of blocks that validator has to wait before unstaking, counted from staking time + uint256 public unstakingOnHoldBlocksNum; + + // TODO: expose this fn in the interface + /// @dev Configuration of minimum balance for being a validator + uint256 public minValidatorBalance; + + uint256[] internal currentValidatorIndexes; + + IValidatorSet validatorSetContract; + + uint256 public numOfCabinets; + + modifier onlyValidatorSetContract() { + require(msg.sender == address(validatorSetContract), "Only validator set contract"); + _; + } + + constructor() { + _disableInitializers(); + } + + /** + * @dev Initializes the contract storage. + */ + function initialize() external initializer { + /// TODO: bring this constant variable to params. + totalValidatorThreshold = 50; + unstakingOnHoldBlocksNum = 28800; // 28800 blocks ~= 1 day + minValidatorBalance = 3 * 1e6 * 1e18; // 3m RON + /// Add empty validator at 0-index + validatorCandidates.push(); + } + + /** + * @dev See {IStaking-proposeValidator}. + */ + function proposeValidator( + address _consensusAddr, + address payable _treasuryAddr, + uint256 _commissionRate + ) external payable returns (uint256 _candidateIdx) { + uint256 _amount = msg.value; + address _stakingAddr = msg.sender; + + require(validatorCandidates.length < totalValidatorThreshold, "Staking: query for exceeded validator array length"); + require(_validatorIndexes[_consensusAddr] == 0, "Staking: query for existed candidate"); + require(_amount >= minValidatorBalance, "Staking: insuficient amount"); + require(_treasuryAddr != address(0), "Staking: invalid treasury address"); + + ValidatorCandidate storage _candidate = validatorCandidates.push(); + _candidateIdx = validatorCandidates.length; + _validatorIndexes[_consensusAddr] = _candidateIdx; + _candidate.consensusAddr = _consensusAddr; + _candidate.stakingAddr = _stakingAddr; + _candidate.treasuryAddr = _treasuryAddr; + _candidate.commissionRate = _commissionRate; + _candidate.stakedAmount = _amount; + + emit ValidatorProposed(_consensusAddr, _stakingAddr, _amount, _candidate); + } + + /** + * @dev See {IStaking-stake}. + */ + function stake(address _consensusAddr) external payable { + uint256 _amount = msg.value; + ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); + require(_candidate.stakingAddr == msg.sender, "Staking: invalid staking address"); + + _candidate.stakedAmount += _amount; + emit Staked(_consensusAddr, _amount); + } + + /** + * @dev See {IStaking-unstake}. + */ + function unstake(address _consensusAddr, uint256 _amount) external { + ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); + require(_candidate.stakingAddr == msg.sender, "Staking: invalid staking address"); + require(_amount < _candidate.stakedAmount, "Staking: insufficient staked amount"); + + uint256 remainAmount = _candidate.stakedAmount - _amount; + require(remainAmount >= minValidatorBalance, "Staking: invalid staked amount left"); + + _candidate.stakedAmount = _amount; + emit Unstaked(_consensusAddr, _amount); + } + + /** + * @dev See {IStaking-delegate}. + */ + function delegate(address _consensusAddr) public payable { + _delegate(msg.sender, _consensusAddr, msg.value); + } + + /** + * @dev See {IStaking-delegate}. + */ + function undelegate(address _consensusAddr, uint256 _amount) public { + _undelegate(msg.sender, _consensusAddr, _amount); + require(payable(msg.sender).send(_amount), "Staking: could not transfer RON"); + } + + /** + * @dev TODO: move to IStaking.sol + */ + function redelegate( + address _consensusAddrSrc, + address _consensusAddrDst, + uint256 _amount + ) external { + _undelegate(msg.sender, _consensusAddrSrc, _amount); + _delegate(msg.sender, _consensusAddrDst, _amount); + } + + /** + * @dev TODO + */ + function getRewards(address[] calldata _consensusAddrList) + external + view + returns (uint256[] memory _pending, uint256[] memory _claimable) + { + revert("Unimplemented"); + } + + /** + * @dev Claims rewards. + */ + function claimRewards(address[] calldata _consensusAddrList, uint256 _amounts) external returns (uint256 _amount) { + revert("Unimplemented"); + } + + /** + * @dev Claims all pending rewards and delegates them to the consensus address. + */ + function delegateRewards(address[] calldata _consensusAddrList, address _consensusAddrDst) + external + returns (uint256 _amount) + { + revert("Unimplemented"); + } + + function onDeposit() external payable override { + revert("Unimplemented"); + } + + function allocateReward(address valAddr, uint256 amount) external override { + revert("Unimplemented"); + } + + function receiveReward(address valAddr) external payable override { + revert("Unimplemented"); + } + + function claimReward(uint256 amount) external override { + revert("Unimplemented"); + } + + function slash(address valAddr, uint256 amount) external override { + revert("Unimplemented"); + } + + /** + * @dev Delegates from a validator address. + * + * Requirements: + * - The validator is an existed candidate. + * + * Emits the `Delegated` event. + * + * @notice This function does not verify the `msg.value` with the amount. + */ + function _delegate( + address _delegator, + address _consensusAddr, + uint256 _amount + ) internal { + ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); + _candidate.delegatedAmount += _amount; + delegatedAmount[_delegator][_consensusAddr] += _amount; + + emit Delegated(_delegator, _consensusAddr, _amount); + } + + /** + * @dev Undelegates from a validator address. + * + * Requirements: + * - The validator is an existed candidate. + * - The delegated amount is larger than or equal to the undelegated amount. + * + * Emits the `Undelegated` event. + * + * @notice Consider transferring back the amount of RON after calling this function. + */ + function _undelegate( + address _delegator, + address _consensusAddr, + uint256 _amount + ) internal { + require(delegatedAmount[_delegator][_consensusAddr] >= _amount, "Staking: insufficient amount to undelegate"); + + ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); + _candidate.delegatedAmount -= _amount; + delegatedAmount[_delegator][_consensusAddr] -= _amount; + + emit Undelegated(_delegator, _consensusAddr, _amount); + } + + /** + * @dev Returns the validator candidate in form storage. + * + * Requirements: + * - The candidate is already existed. + * + */ + function _getCandidate(address _consensusAddr) internal view returns (ValidatorCandidate storage _candidate) { + uint256 _idx = _validatorIndexes[_consensusAddr]; + require(_idx > 0, "Staking: query for nonexistent candidate"); + _candidate = validatorCandidates[_idx]; + } + + /** + * @notice Update set of validators + * + * @dev Sorting the validators by their current balance, then pick the top N validators to be + * assigned to the new set. The result is returned to the `ValidatorSet` contract. + * + * Requirements: + * - Only validator and `ValidatorSet` contract can call this function + * + * @return newValidatorSet Validator set for the new epoch + */ + function updateValidatorSet() external returns (ValidatorCandidate[] memory newValidatorSet) { + uint _length = validatorCandidates.length; + Sorting.Node[] memory _nodes = new Sorting.Node[](_length); + Sorting.Node[] memory _sortedNodes = new Sorting.Node[](_length); + + for (uint i = 0; i < _length; i++) { + ValidatorCandidate memory _validator = validatorCandidates[i]; + + _nodes[i].key = i; + _nodes[i].value = _validator.stakedAmount + _validator.delegatedAmount; + } + + delete currentValidatorIndexes; + _sortedNodes = Sorting.sortNodes(_nodes); + + /// TODO: pick M validators which are governance + uint _currentSetSize = _length < numOfCabinets ? _length : numOfCabinets; + for (uint i = 0; i < _currentSetSize; i++) { + currentValidatorIndexes.push(_sortedNodes[i].value); + } + + return getCurrentValidatorSet(); + } + + function getCurrentValidatorSet() public view returns (ValidatorCandidate[] memory currentValidatorSet_) { + uint _length = currentValidatorIndexes.length; + currentValidatorSet_ = new ValidatorCandidate[](currentValidatorIndexes.length); + for (uint i = 0; i < _length; i++) { + currentValidatorSet_[i] = validatorCandidates[currentValidatorIndexes[i]]; + } + + return currentValidatorSet_; + } +} From 5ce6b6628806f313af39c7d4969f594da96b6a03 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Sat, 3 Sep 2022 17:27:15 +0700 Subject: [PATCH 040/190] [Staking] Fix staking bug. Refactor test. --- contracts/Staking.sol | 13 +- .../sorting/MockSwapStorage.test.ts | 0 test/staking/Staking.test.ts | 136 ++++++++++++++++++ .../validators/ValidatorSetCore.test.ts | 0 4 files changed, 143 insertions(+), 6 deletions(-) rename {tests => test}/sorting/MockSwapStorage.test.ts (100%) create mode 100644 test/staking/Staking.test.ts rename {tests => test}/validators/ValidatorSetCore.test.ts (100%) diff --git a/contracts/Staking.sol b/contracts/Staking.sol index d81b56561..4ce774a59 100644 --- a/contracts/Staking.sol +++ b/contracts/Staking.sol @@ -73,8 +73,8 @@ contract Staking is IStaking, Initializable { require(_amount >= minValidatorBalance, "Staking: insuficient amount"); require(_treasuryAddr != address(0), "Staking: invalid treasury address"); - ValidatorCandidate storage _candidate = validatorCandidates.push(); _candidateIdx = validatorCandidates.length; + ValidatorCandidate storage _candidate = validatorCandidates.push(); _validatorIndexes[_consensusAddr] = _candidateIdx; _candidate.consensusAddr = _consensusAddr; _candidate.stakingAddr = _stakingAddr; @@ -102,13 +102,14 @@ contract Staking is IStaking, Initializable { */ function unstake(address _consensusAddr, uint256 _amount) external { ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); - require(_candidate.stakingAddr == msg.sender, "Staking: invalid staking address"); - require(_amount < _candidate.stakedAmount, "Staking: insufficient staked amount"); + require(_candidate.stakingAddr == msg.sender, "Staking: caller must be staking address"); + uint256 _maxUnstake = _candidate.stakedAmount - minValidatorBalance; + require(_amount <= _maxUnstake, "Staking: invalid staked amount left"); - uint256 remainAmount = _candidate.stakedAmount - _amount; - require(remainAmount >= minValidatorBalance, "Staking: invalid staked amount left"); + _candidate.stakedAmount -= _amount; + (bool _success, ) = msg.sender.call{value: _amount}(""); + require(_success, "Staking: transfer unstaking failed"); - _candidate.stakedAmount = _amount; emit Unstaked(_consensusAddr, _amount); } diff --git a/tests/sorting/MockSwapStorage.test.ts b/test/sorting/MockSwapStorage.test.ts similarity index 100% rename from tests/sorting/MockSwapStorage.test.ts rename to test/sorting/MockSwapStorage.test.ts diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts new file mode 100644 index 000000000..2b43c63d0 --- /dev/null +++ b/test/staking/Staking.test.ts @@ -0,0 +1,136 @@ +import { expect } from 'chai'; +import { ethers, deployments } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; + +import { Staking, Staking__factory } from '../../src/types'; +import { ValidatorCandidateStruct } from '../../src/types/ValidatorSetCoreMock'; +import { BigNumber, BigNumberish } from 'ethers'; + +let stakingContract: Staking; + +let signers: SignerWithAddress[]; +let admin: SignerWithAddress; +let candidates: ValidatorCandidateStruct[]; +let stakingAddrs: SignerWithAddress[]; +let consensusAddrs: SignerWithAddress[]; +let treasuryAddrs: SignerWithAddress[]; + +const generateCandidate = ( + stakingAddr: string, + consensusAddr: string, + treasuryAddr: string +): ValidatorCandidateStruct => { + return { + stakingAddr: stakingAddr, + consensusAddr: consensusAddr, + treasuryAddr: treasuryAddr, + commissionRate: 0, + stakedAmount: 0, + delegatedAmount: 0, + governing: false, + ____gap: Array.apply(null, Array(20)).map((_) => 0), + }; +}; + +describe('PoS Staking test', () => { + let unstakingOnHoldBlocksNum: BigNumber; + let minValidatorBalance: BigNumber; + + describe('Single flow', async () => { + before(async () => { + [admin, ...signers] = await ethers.getSigners(); + await deployments.fixture('StakingContract'); + const stakingProxyDeployment = await deployments.get('StakingProxy'); + stakingContract = Staking__factory.connect(stakingProxyDeployment.address, admin); + + candidates = []; + stakingAddrs = []; + consensusAddrs = []; + treasuryAddrs = []; + + unstakingOnHoldBlocksNum = await stakingContract.unstakingOnHoldBlocksNum(); + minValidatorBalance = await stakingContract.minValidatorBalance(); + + console.log('Init addresses...'); + for (let i = 0; i < 10; i++) { + candidates.push( + generateCandidate(signers[3 * i].address, signers[3 * i + 1].address, signers[3 * i + 2].address) + ); + stakingAddrs.push(signers[3 * i]); + consensusAddrs.push(signers[3 * i + 1]); + treasuryAddrs.push(signers[3 * i + 2]); + + console.log( + i, + signers[3 * i].address, + signers[3 * i + 1].address, + signers[3 * i + 2].address, + (await ethers.provider.getBalance(signers[3 * i].address)).toString(), + (await ethers.provider.getBalance(signers[3 * i + 1].address)).toString(), + (await ethers.provider.getBalance(signers[3 * i + 2].address)).toString() + ); + } + }); + + describe('Validator functions', async () => { + it('Should be able to propose 1 validator', async () => { + let tx = await stakingContract + .connect(stakingAddrs[0]) + .proposeValidator(consensusAddrs[0].address, treasuryAddrs[0].address, 0, { + value: minValidatorBalance.toString(), + }); + expect(await tx) + .to.emit(stakingContract, 'ValidatorProposed') + .withArgs(consensusAddrs[0].address, stakingAddrs[0].address, minValidatorBalance, candidates[0]); + }); + + it('Should not be able to propose 1 validator - insufficient fund', async () => { + let tx = stakingContract + .connect(stakingAddrs[1]) + .proposeValidator(consensusAddrs[1].address, treasuryAddrs[1].address, 0, { + value: minValidatorBalance.sub(1).toString(), + }); + await expect(tx).to.revertedWith('Staking: insuficient amount'); + }); + + it('Should not be able to propose 1 validator - duplicated validator', async () => { + let tx = stakingContract + .connect(stakingAddrs[0]) + .proposeValidator(consensusAddrs[0].address, treasuryAddrs[0].address, 0, { + value: minValidatorBalance.toString(), + }); + await expect(tx).to.revertedWith('Staking: query for existed candidate'); + }); + + it('Should be able to stake for a validator', async () => { + let stakingValue = ethers.utils.parseEther('1.0'); + let tx = stakingContract.connect(stakingAddrs[0]).stake(consensusAddrs[0].address, { + value: stakingValue, + }); + await expect(tx).to.emit(stakingContract, 'Staked').withArgs(consensusAddrs[0].address, stakingValue); + }); + + it('Should not be able to unstake - exceeds minimum balance', async () => { + let unstakingValue = ethers.utils.parseEther('1.1'); + let tx = stakingContract.connect(stakingAddrs[0]).unstake(consensusAddrs[0].address, unstakingValue); + await expect(tx).to.revertedWith('Staking: invalid staked amount left'); + }); + + it('Should not be able to unstake - caller is not staking address', async () => { + let unstakingValue = ethers.utils.parseEther('1.0'); + let tx = stakingContract.connect(stakingAddrs[1]).unstake(consensusAddrs[0].address, unstakingValue); + await expect(tx).to.revertedWith('Staking: caller must be staking address'); + }); + + it('Should be able to unstake', async () => { + let unstakingValue = ethers.utils.parseEther('1.0'); + let tx = stakingContract.connect(stakingAddrs[0]).unstake(consensusAddrs[0].address, unstakingValue); + await expect(tx).to.emit(stakingContract, 'Unstaked').withArgs(consensusAddrs[0].address, unstakingValue); + }); + }); + + describe('Delegator functions', async () => {}); + + describe('Internal fucntions', async () => {}); + }); +}); diff --git a/tests/validators/ValidatorSetCore.test.ts b/test/validators/ValidatorSetCore.test.ts similarity index 100% rename from tests/validators/ValidatorSetCore.test.ts rename to test/validators/ValidatorSetCore.test.ts From 4ea42331080b1945b9cd658c0ace8a004e1eadec Mon Sep 17 00:00:00 2001 From: nxqbao Date: Sat, 3 Sep 2022 17:27:32 +0700 Subject: [PATCH 041/190] [hardhat] Update initial balance --- hardhat.config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 7e132c662..2ee51bc46 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -63,7 +63,8 @@ const config: HardhatUserConfig = { hardhat: { accounts: { mnemonic: DEFAULT_MNEMONIC, - count: 10, + count: 100, + accountsBalance: '1000000000000000000000000000' // 1B RON }, }, 'ronin-testnet': testnet, From dc57fd10e6dd0351a63429a75bd3ff33815bdadc Mon Sep 17 00:00:00 2001 From: nxqbao Date: Sun, 4 Sep 2022 17:59:39 +0700 Subject: [PATCH 042/190] [Validator] Refactor --- contracts/mocks/MockValidatorSetCore.sol | 2 +- contracts/{ => validator}/ValidatorSet.sol | 6 ++--- .../{ => validator}/ValidatorSetCore.sol | 26 ++++++++++++------- .../ValidatorSetCore.test.ts | 0 4 files changed, 21 insertions(+), 13 deletions(-) rename contracts/{ => validator}/ValidatorSet.sol (97%) rename contracts/{ => validator}/ValidatorSetCore.sol (86%) rename test/{validators => validator}/ValidatorSetCore.test.ts (100%) diff --git a/contracts/mocks/MockValidatorSetCore.sol b/contracts/mocks/MockValidatorSetCore.sol index 330a6ef45..f90c20088 100644 --- a/contracts/mocks/MockValidatorSetCore.sol +++ b/contracts/mocks/MockValidatorSetCore.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.9; -import "../ValidatorSetCore.sol"; +import "../validator/ValidatorSetCore.sol"; import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; /** diff --git a/contracts/ValidatorSet.sol b/contracts/validator/ValidatorSet.sol similarity index 97% rename from contracts/ValidatorSet.sol rename to contracts/validator/ValidatorSet.sol index 82a0e5d40..dd66bc350 100644 --- a/contracts/ValidatorSet.sol +++ b/contracts/validator/ValidatorSet.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import "./interfaces/ISlashIndicator.sol"; -import "./interfaces/IStaking.sol"; -import "./interfaces/IValidatorSet.sol"; +import "../interfaces/ISlashIndicator.sol"; +import "../interfaces/IStaking.sol"; +import "../interfaces/IValidatorSet.sol"; import "./ValidatorSetCore.sol"; /** diff --git a/contracts/ValidatorSetCore.sol b/contracts/validator/ValidatorSetCore.sol similarity index 86% rename from contracts/ValidatorSetCore.sol rename to contracts/validator/ValidatorSetCore.sol index 67b702d03..bca9e794e 100644 --- a/contracts/ValidatorSetCore.sol +++ b/contracts/validator/ValidatorSetCore.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import "./interfaces/IValidatorSet.sol"; -import "./interfaces/IStaking.sol"; +import "../interfaces/IValidatorSet.sol"; +import "../interfaces/IStaking.sol"; /** * @title Set of validators in current epoch @@ -13,20 +13,28 @@ import "./interfaces/IStaking.sol"; abstract contract ValidatorSetCore { using EnumerableMap for EnumerableMap.AddressToUintMap; - /// @dev Array of all validators. The element at 0-slot is reserved for unknown validator. + /// @dev Array of all validators. + /// + /// The element at 0-slot is reserved for unknown validator. Index of each validator must be + /// immutable, since this array is synced per each epoch from the Staking contract. IValidatorSet.Validator[] public validatorSet; + /// @dev Map of array of all validators mapping(address => uint) public validatorSetMap; - /// @dev Enumerable map of indexes of the current validator set, e.g. the array [0, 3, 4, 1] tells - /// the sequence of validators to mine block in the next epoch will be at the 3-, 4- and 1-index, - /// One can query the mining order of an arbitrary address by this enumerable map. This array is - /// indexed from 1, and its content is non-zero value, due to reserved 0-slot in `validatorSet`. + + /// @dev Enumerable map of indexes of the current validator set. + /// + /// The array of [0, 3, 4, 1] means the sequence of validators to mine block in the next epoch + /// will be at the 3-, 4- and 1-index, first element (0-index) is ignored. One can query the + /// mining order of an arbitrary address by this enumerable map. This array is indexed from 1, + /// and it contains non-zero value, due to reserved 0-slot in the `validatorSet` array. uint[] internal currentValidatorIndexes; mapping(address => uint) currentValidatorIndexesMap; constructor() { // Add empty validator at 0-slot for the set map validatorSet.push(); + // Add reserved 0-index for the current validator set currentValidatorIndexes.push(); } @@ -80,9 +88,9 @@ abstract contract ValidatorSetCore { function _popValidatorFromMiningIndex() internal { uint _length = currentValidatorIndexes.length - 1; - require(_length > 1, "Validator: Cannot remove the last element"); + require(_length > 1, "Validator: Cannot remove the last element"); - IValidatorSet.Validator storage _lastValidatorInEpoch = validatorSet[currentValidatorIndexes[_length]]; + IValidatorSet.Validator storage _lastValidatorInEpoch = validatorSet[currentValidatorIndexes[_length]]; currentValidatorIndexesMap[_lastValidatorInEpoch.consensusAddr] = 0; currentValidatorIndexes.pop(); } diff --git a/test/validators/ValidatorSetCore.test.ts b/test/validator/ValidatorSetCore.test.ts similarity index 100% rename from test/validators/ValidatorSetCore.test.ts rename to test/validator/ValidatorSetCore.test.ts From 0e8ac5b4c9317cfe58cb731e1177dc81ef40cf38 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Sun, 4 Sep 2022 18:39:57 +0700 Subject: [PATCH 043/190] [Staking] Add renounce validator. Add test. --- contracts/interfaces/IStaking.sol | 10 ++ contracts/{ => staking}/Staking.sol | 171 +++++++++++++++++++++---- test/staking/Staking.test.ts | 190 ++++++++++++++++++++++------ 3 files changed, 305 insertions(+), 66 deletions(-) rename contracts/{ => staking}/Staking.sol (57%) diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index a944d581e..897de1ed3 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -3,6 +3,12 @@ pragma solidity ^0.8.9; interface IStaking { + enum ValidatorState { + ACTIVE, + ON_RENOUNCE, + RENOUNCED + } + struct ValidatorCandidate { /// @dev The candidate admin that stakes for the validator. address candidateAdmin; @@ -19,6 +25,8 @@ interface IStaking { uint256 delegatedAmount; /// @dev Mark the validator is a governance node bool governing; + /// @dev State of the validator + ValidatorState state; /// @dev For upgrability purpose uint256[20] ____gap; } @@ -32,6 +40,8 @@ interface IStaking { ); event Staked(address indexed validator, uint256 amount); event Unstaked(address indexed validator, uint256 amount); + event ValidatorRenounceRequested(address indexed consensusAddr, uint256 amount); + event ValidatorRenounceFinalized(address indexed consensusAddr, uint256 amount); event Delegated(address indexed delegator, address indexed validator, uint256 amount); event Undelegated(address indexed delegator, address indexed validator, uint256 amount); diff --git a/contracts/Staking.sol b/contracts/staking/Staking.sol similarity index 57% rename from contracts/Staking.sol rename to contracts/staking/Staking.sol index 4ce774a59..cd50143fc 100644 --- a/contracts/Staking.sol +++ b/contracts/staking/Staking.sol @@ -3,17 +3,22 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "./interfaces/IStaking.sol"; -import "./interfaces/IValidatorSet.sol"; -import "./libraries/Sorting.sol"; +import "../interfaces/IStaking.sol"; +import "../interfaces/IValidatorSet.sol"; +import "../libraries/Sorting.sol"; +import "hardhat/console.sol"; contract Staking is IStaking, Initializable { /// TODO: expose fn to get validator info by validator address. /// @dev Mapping from consensus address => validator index in `validatorCandidates`. mapping(address => uint256) internal _validatorIndexes; - /// TODO: expose fn returns the whole validator arry. - /// @dev Validator array. + /// TODO: expose fn returns the whole validator array. + /// @dev Validator array. The order of the validator is assured not to be changed, since this + /// array is kept synced with the array in the `Staking` contract. ValidatorCandidate[] public validatorCandidates; + + /// @dev Index of validators that are on renounce + uint256[] public pendingRenoucingValidatorIndexes; /// @dev Mapping from delegator address => consensus address => delegated amount. mapping(address => mapping(address => uint256)) delegatedAmount; @@ -23,7 +28,8 @@ contract Staking is IStaking, Initializable { uint256 public totalValidatorThreshold; // TODO: expose this fn in the interface - /// @dev Configuration of number of blocks that validator has to wait before unstaking, counted from staking time + /// @dev Configuration of number of blocks that validator has to wait before unstaking, counted + /// from staking time uint256 public unstakingOnHoldBlocksNum; // TODO: expose this fn in the interface @@ -34,6 +40,10 @@ contract Staking is IStaking, Initializable { IValidatorSet validatorSetContract; + /// @dev Helper global flag which is set to true on at least one validator balance changes, and + /// is reset to false per epoch. This help reduces redundant sortings. + bool stateChanged; + uint256 public numOfCabinets; modifier onlyValidatorSetContract() { @@ -69,14 +79,20 @@ contract Staking is IStaking, Initializable { address _stakingAddr = msg.sender; require(validatorCandidates.length < totalValidatorThreshold, "Staking: query for exceeded validator array length"); - require(_validatorIndexes[_consensusAddr] == 0, "Staking: query for existed candidate"); require(_amount >= minValidatorBalance, "Staking: insuficient amount"); require(_treasuryAddr != address(0), "Staking: invalid treasury address"); - _candidateIdx = validatorCandidates.length; - ValidatorCandidate storage _candidate = validatorCandidates.push(); - _validatorIndexes[_consensusAddr] = _candidateIdx; - _candidate.consensusAddr = _consensusAddr; + ValidatorCandidate storage _candidate; + if (_validatorIndexes[_consensusAddr] > 0) { /// renounced validator joins as a validator again + (, _candidate) = _getDeprecatedCandidate(_consensusAddr); + require(_candidate.state == ValidatorState.RENOUNCED, "Staking: cannot propose an existed candidate"); + } else { /// totally new validator joins + _candidateIdx = validatorCandidates.length; + _candidate = validatorCandidates.push(); + _validatorIndexes[_consensusAddr] = _candidateIdx; + _candidate.consensusAddr = _consensusAddr; + } + _candidate.stakingAddr = _stakingAddr; _candidate.treasuryAddr = _treasuryAddr; _candidate.commissionRate = _commissionRate; @@ -90,7 +106,10 @@ contract Staking is IStaking, Initializable { */ function stake(address _consensusAddr) external payable { uint256 _amount = msg.value; - ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); + (, ValidatorCandidate storage _candidate) = _getCandidate(_consensusAddr); + console.log("[*] Staking"); + console.log("[ ] \t stake address:\t", _candidate.stakingAddr); + console.log("[ ] \t consensus address:\t", _candidate.consensusAddr); require(_candidate.stakingAddr == msg.sender, "Staking: invalid staking address"); _candidate.stakedAmount += _amount; @@ -101,18 +120,78 @@ contract Staking is IStaking, Initializable { * @dev See {IStaking-unstake}. */ function unstake(address _consensusAddr, uint256 _amount) external { - ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); + (, ValidatorCandidate storage _candidate) = _getCandidate(_consensusAddr); require(_candidate.stakingAddr == msg.sender, "Staking: caller must be staking address"); - uint256 _maxUnstake = _candidate.stakedAmount - minValidatorBalance; + uint256 _maxUnstake = _candidate.stakedAmount - minValidatorBalance; require(_amount <= _maxUnstake, "Staking: invalid staked amount left"); _candidate.stakedAmount -= _amount; - (bool _success, ) = msg.sender.call{value: _amount}(""); - require(_success, "Staking: transfer unstaking failed"); + _transferRON(msg.sender, _amount); emit Unstaked(_consensusAddr, _amount); } + /** + * @notice Allow validator sends renouncing request. The request gets affected when the epoch ends + * + * @dev The following procedure must be done in multiple methods in order to finish the renounce. + * + * 1. This method: + * - Set `ValidatorState` of validator to `ON_RENOUNCE`; + * - Push the validator to a pending list; + * - Trigger the `stateChanged` flag. + * + * 2. The `updateValidatorSet` method: + * - Set the balance-to-sort of the validator to `0`; + * - Reset the `stateChanged`s flag. + * + * 3. The `finalizeRenouncingValidator` method: + * - Remove validator from pending list + * - Set `ValidatorState` of validator to `RENOUNCED`; + * - Set `stakedAmount` of validator to `0`; + * - Transfer the staked to the respective staking address. + * + * Requirements: + * - The validator must be exist + * - The validator must be in `ACTIVE` state + * + */ + function requestRenouncingValidator(address _consensusAddr) external { + (uint256 _index, ValidatorCandidate storage _candidate) = _getCandidate(_consensusAddr); + require(_candidate.stakingAddr == msg.sender, "Staking: caller must be staking address"); + + stateChanged = true; + _candidate.state = ValidatorState.ON_RENOUNCE; + pendingRenoucingValidatorIndexes.push(_index); + + emit ValidatorRenounceRequested(_consensusAddr, _candidate.stakedAmount); + } + + function finalizeRenouncingValidator(address _consensusAddr) external { + (uint256 _index, ValidatorCandidate storage _candidate) = _getDeprecatedCandidate(_consensusAddr); + require(_candidate.stakingAddr == msg.sender, "Staking: caller must be staking address"); + require(_candidate.state == ValidatorState.ON_RENOUNCE, "Staking: validator state is not ON_RENOUNCE"); + + bool _found; + uint _length = pendingRenoucingValidatorIndexes.length; + uint _amount = _candidate.stakedAmount; + for (uint i; i < _length; ++i) { + if (_index == pendingRenoucingValidatorIndexes[i]) { + _found = true; + pendingRenoucingValidatorIndexes[i] = pendingRenoucingValidatorIndexes[_length - 1]; + pendingRenoucingValidatorIndexes.pop(); + break; + } + } + require(_found, "Staking: cannot finalize not requested renounce"); + + _candidate.state = ValidatorState.RENOUNCED; + _candidate.stakedAmount = 0; + _transferRON(msg.sender, _amount); + + emit ValidatorRenounceFinalized(_consensusAddr, _amount); + } + /** * @dev See {IStaking-delegate}. */ @@ -125,7 +204,7 @@ contract Staking is IStaking, Initializable { */ function undelegate(address _consensusAddr, uint256 _amount) public { _undelegate(msg.sender, _consensusAddr, _amount); - require(payable(msg.sender).send(_amount), "Staking: could not transfer RON"); + _transferRON(msg.sender, _amount); } /** @@ -203,7 +282,7 @@ contract Staking is IStaking, Initializable { address _consensusAddr, uint256 _amount ) internal { - ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); + (, ValidatorCandidate storage _candidate) = _getCandidate(_consensusAddr); _candidate.delegatedAmount += _amount; delegatedAmount[_delegator][_consensusAddr] += _amount; @@ -228,7 +307,7 @@ contract Staking is IStaking, Initializable { ) internal { require(delegatedAmount[_delegator][_consensusAddr] >= _amount, "Staking: insufficient amount to undelegate"); - ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); + (, ValidatorCandidate storage _candidate) = _getDeprecatedCandidate(_consensusAddr); _candidate.delegatedAmount -= _amount; delegatedAmount[_delegator][_consensusAddr] -= _amount; @@ -236,16 +315,47 @@ contract Staking is IStaking, Initializable { } /** - * @dev Returns the validator candidate in form storage. + * @dev Returns the active validator candidate from storage. * * Requirements: * - The candidate is already existed. + * - The candidate is in ACTIVE state * */ - function _getCandidate(address _consensusAddr) internal view returns (ValidatorCandidate storage _candidate) { - uint256 _idx = _validatorIndexes[_consensusAddr]; - require(_idx > 0, "Staking: query for nonexistent candidate"); - _candidate = validatorCandidates[_idx]; + function _getCandidate(address _consensusAddr) + internal + view + returns (uint256 index_, ValidatorCandidate storage candidate_) + { + (index_, candidate_) = _getDeprecatedCandidate(_consensusAddr); + require(candidate_.state == ValidatorState.ACTIVE, "Staking: query for deprecated candidate"); + } + + /** + * @dev Returns the validator candidate from storage, without checking his status. + * + * Requirements: + * - The candidate is already existed. + * + */ + function _getDeprecatedCandidate(address _consensusAddr) + internal + view + returns (uint256 index_, ValidatorCandidate storage candidate_) + { + index_ = _validatorIndexes[_consensusAddr]; + require(index_ > 0, "Staking: query for nonexistent candidate"); + console.log("[*] _getCandidate"); + console.log("[ ] \t consensus address:\t", _consensusAddr); + console.log("[ ] \t index:\t", index_); + candidate_ = validatorCandidates[index_]; + } + + function _transferRON(address _to, uint256 _amount) private { + // Using `call` to remove 2300 gas stipend + // https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/ + (bool _success, ) = _to.call{ value: _amount }(""); + require(_success, "Staking: transfer RON failed"); } /** @@ -260,6 +370,15 @@ contract Staking is IStaking, Initializable { * @return newValidatorSet Validator set for the new epoch */ function updateValidatorSet() external returns (ValidatorCandidate[] memory newValidatorSet) { + console.log("[*] updateValidatorSet"); + if (!stateChanged) { + console.log("[ ] skipped"); + return getCurrentValidatorSet(); + } + + console.log("[ ] sorted"); + stateChanged = false; + uint _length = validatorCandidates.length; Sorting.Node[] memory _nodes = new Sorting.Node[](_length); Sorting.Node[] memory _sortedNodes = new Sorting.Node[](_length); @@ -268,7 +387,9 @@ contract Staking is IStaking, Initializable { ValidatorCandidate memory _validator = validatorCandidates[i]; _nodes[i].key = i; - _nodes[i].value = _validator.stakedAmount + _validator.delegatedAmount; + _nodes[i].value = (_validator.state == ValidatorState.ACTIVE) + ? _validator.stakedAmount + _validator.delegatedAmount + : 0; } delete currentValidatorIndexes; diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index 2b43c63d0..26c190cfb 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -4,7 +4,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { Staking, Staking__factory } from '../../src/types'; import { ValidatorCandidateStruct } from '../../src/types/ValidatorSetCoreMock'; -import { BigNumber, BigNumberish } from 'ethers'; +import { BigNumber } from 'ethers'; let stakingContract: Staking; @@ -73,64 +73,172 @@ describe('PoS Staking test', () => { }); describe('Validator functions', async () => { - it('Should be able to propose 1 validator', async () => { - let tx = await stakingContract - .connect(stakingAddrs[0]) - .proposeValidator(consensusAddrs[0].address, treasuryAddrs[0].address, 0, { - value: minValidatorBalance.toString(), - }); - expect(await tx) - .to.emit(stakingContract, 'ValidatorProposed') - .withArgs(consensusAddrs[0].address, stakingAddrs[0].address, minValidatorBalance, candidates[0]); + describe('Proposing', async () => { + it('Should be able to propose 1 validator', async () => { + let tx = await stakingContract + .connect(stakingAddrs[0]) + .proposeValidator(consensusAddrs[0].address, treasuryAddrs[0].address, 0, { + value: minValidatorBalance.toString(), + }); + expect(await tx) + .to.emit(stakingContract, 'ValidatorProposed') + .withArgs(consensusAddrs[0].address, stakingAddrs[0].address, minValidatorBalance, candidates[0]); + }); + + it('Should not be able to propose 1 validator - insufficient fund', async () => { + let tx = stakingContract + .connect(stakingAddrs[1]) + .proposeValidator(consensusAddrs[1].address, treasuryAddrs[1].address, 0, { + value: minValidatorBalance.sub(1).toString(), + }); + await expect(tx).to.revertedWith('Staking: insuficient amount'); + }); + + it('Should not be able to propose 1 validator - duplicated validator', async () => { + let tx = stakingContract + .connect(stakingAddrs[0]) + .proposeValidator(consensusAddrs[0].address, treasuryAddrs[0].address, 0, { + value: minValidatorBalance.toString(), + }); + await expect(tx).to.revertedWith('Staking: cannot propose an existed candidate'); + }); }); - it('Should not be able to propose 1 validator - insufficient fund', async () => { - let tx = stakingContract - .connect(stakingAddrs[1]) - .proposeValidator(consensusAddrs[1].address, treasuryAddrs[1].address, 0, { - value: minValidatorBalance.sub(1).toString(), + describe('Staking', async () => { + it('Should be able to stake for a validator', async () => { + let stakingValue = ethers.utils.parseEther('1.0'); + let tx = stakingContract.connect(stakingAddrs[0]).stake(consensusAddrs[0].address, { + value: stakingValue, }); - await expect(tx).to.revertedWith('Staking: insuficient amount'); - }); + await expect(tx).to.emit(stakingContract, 'Staked').withArgs(consensusAddrs[0].address, stakingValue); + }); - it('Should not be able to propose 1 validator - duplicated validator', async () => { - let tx = stakingContract - .connect(stakingAddrs[0]) - .proposeValidator(consensusAddrs[0].address, treasuryAddrs[0].address, 0, { - value: minValidatorBalance.toString(), + it('Should not be able to stake for an unexistent validator', async () => { + let stakingValue = ethers.utils.parseEther('1.0'); + let tx = stakingContract.connect(stakingAddrs[0]).stake(consensusAddrs[1].address, { + value: stakingValue, }); - await expect(tx).to.revertedWith('Staking: query for existed candidate'); + await expect(tx).to.revertedWith('Staking: query for nonexistent candidate'); + }); }); - it('Should be able to stake for a validator', async () => { - let stakingValue = ethers.utils.parseEther('1.0'); - let tx = stakingContract.connect(stakingAddrs[0]).stake(consensusAddrs[0].address, { - value: stakingValue, + describe('Unstaking', async () => { + it('Should not be able to unstake - exceeds minimum balance', async () => { + let unstakingValue = ethers.utils.parseEther('1.1'); + let tx = stakingContract.connect(stakingAddrs[0]).unstake(consensusAddrs[0].address, unstakingValue); + await expect(tx).to.revertedWith('Staking: invalid staked amount left'); + }); + + it('Should not be able to unstake - caller is not staking address', async () => { + let unstakingValue = ethers.utils.parseEther('1.0'); + let tx = stakingContract.connect(stakingAddrs[1]).unstake(consensusAddrs[0].address, unstakingValue); + await expect(tx).to.revertedWith('Staking: caller must be staking address'); + }); + + it('Should be able to unstake', async () => { + let tx; + let unstakingValue = ethers.utils.parseEther('1.0'); + let descresingValue = ethers.utils.parseEther('-1.0'); + await expect( + () => (tx = stakingContract.connect(stakingAddrs[0]).unstake(consensusAddrs[0].address, unstakingValue)) + ).to.changeEtherBalances([stakingAddrs[0], stakingContract], [unstakingValue, descresingValue]); + await expect(tx).to.emit(stakingContract, 'Unstaked').withArgs(consensusAddrs[0].address, unstakingValue); + }); + + it('Should not be able to unstake - exceeds minimum balance 2', async () => { + let unstakingValue = 1; + let tx = stakingContract.connect(stakingAddrs[0]).unstake(consensusAddrs[0].address, unstakingValue); + await expect(tx).to.revertedWith('Staking: invalid staked amount left'); }); - await expect(tx).to.emit(stakingContract, 'Staked').withArgs(consensusAddrs[0].address, stakingValue); }); - it('Should not be able to unstake - exceeds minimum balance', async () => { - let unstakingValue = ethers.utils.parseEther('1.1'); - let tx = stakingContract.connect(stakingAddrs[0]).unstake(consensusAddrs[0].address, unstakingValue); - await expect(tx).to.revertedWith('Staking: invalid staked amount left'); + describe('Requesting renounce', async () => { + it('Should not be able to request renounce validator - caller is not staking address', async () => { + let tx = stakingContract.connect(stakingAddrs[1]).requestRenouncingValidator(consensusAddrs[0].address); + await expect(tx).to.revertedWith('Staking: caller must be staking address'); + }); + + it('Should be able to request renounce validator', async () => { + let stakingValue = ethers.utils.parseEther('1.0'); + await stakingContract.connect(stakingAddrs[0]).stake(consensusAddrs[0].address, { + value: stakingValue, + }); + + let tx = stakingContract.connect(stakingAddrs[0]).requestRenouncingValidator(consensusAddrs[0].address); + await expect(tx) + .to.emit(stakingContract, 'ValidatorRenounceRequested') + .withArgs(consensusAddrs[0].address, minValidatorBalance.add(stakingValue)); + }); + + it('Should not be able to request renounce validator twice', async () => { + let tx = stakingContract.connect(stakingAddrs[0]).requestRenouncingValidator(consensusAddrs[0].address); + await expect(tx).to.revertedWith('Staking: query for deprecated candidate'); + }); + + it('Should not be able to propose the validator that is on renounce', async () => { + let tx = stakingContract + .connect(stakingAddrs[0]) + .proposeValidator(consensusAddrs[0].address, treasuryAddrs[0].address, 0, { + value: minValidatorBalance.toString(), + }); + await expect(tx).to.revertedWith('Staking: cannot propose an existed candidate'); + }); + + it('Should not be able to stake for the validator that is on renounce', async () => { + let stakingValue = ethers.utils.parseEther('1.0'); + let tx = stakingContract.connect(stakingAddrs[0]).stake(consensusAddrs[0].address, { + value: stakingValue, + }); + + await expect(tx).to.revertedWith('Staking: query for deprecated candidate'); + }); + + it('Should not be able to unstake for the validator that is on renounce', async () => { + let unstakingValue = ethers.utils.parseEther('1.0'); + let tx = stakingContract.connect(stakingAddrs[1]).unstake(consensusAddrs[0].address, unstakingValue); + await expect(tx).to.revertedWith('Staking: query for deprecated candidate'); + }); }); - it('Should not be able to unstake - caller is not staking address', async () => { - let unstakingValue = ethers.utils.parseEther('1.0'); - let tx = stakingContract.connect(stakingAddrs[1]).unstake(consensusAddrs[0].address, unstakingValue); - await expect(tx).to.revertedWith('Staking: caller must be staking address'); + describe('Finalize renounce', async () => { + it('Should not be able to finalize the renounce - caller is not staking address', async () => { + let tx = stakingContract.connect(stakingAddrs[1]).finalizeRenouncingValidator(consensusAddrs[0].address); + await expect(tx).to.revertedWith('Staking: caller must be staking address'); + }); + + it('Should be able to finalize the renounce', async () => { + let tx; + let incValue = minValidatorBalance.add(ethers.utils.parseEther('1')); + let decValue = BigNumber.from(0).sub(incValue); + await expect( + () => (tx = stakingContract.connect(stakingAddrs[0]).finalizeRenouncingValidator(consensusAddrs[0].address)) + ).to.changeEtherBalances([stakingAddrs[0], stakingContract], [incValue, decValue]); + await expect(tx) + .to.emit(stakingContract, 'ValidatorRenounceFinalized') + .withArgs(consensusAddrs[0].address, incValue); + }); + + it('Should not be able to finalize renounce twice', async () => { + let tx = stakingContract.connect(stakingAddrs[0]).finalizeRenouncingValidator(consensusAddrs[0].address); + await expect(tx).to.revertedWith('Staking: validator state is not ON_RENOUNCE'); + }); + + it('Should not be able to stake for a renounced validator', async () => { + let stakingValue = ethers.utils.parseEther('1.0'); + let tx = stakingContract.connect(stakingAddrs[0]).stake(consensusAddrs[0].address, { + value: stakingValue, + }); + await expect(tx).to.revertedWith('Staking: query for deprecated candidate'); + }); }); - it('Should be able to unstake', async () => { - let unstakingValue = ethers.utils.parseEther('1.0'); - let tx = stakingContract.connect(stakingAddrs[0]).unstake(consensusAddrs[0].address, unstakingValue); - await expect(tx).to.emit(stakingContract, 'Unstaked').withArgs(consensusAddrs[0].address, unstakingValue); + describe('Propose and renounce multiple validator', async () => { + // TODO: }); }); describe('Delegator functions', async () => {}); - describe('Internal fucntions', async () => {}); + describe('Updating validator fucntions', async () => {}); }); }); From e5aa5f8742e4b8ebf15db07125f3c2c3aebf4e12 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Sun, 4 Sep 2022 18:55:55 +0700 Subject: [PATCH 044/190] [Staking] Resolve conflict --- contracts/staking/Staking.sol | 85 ++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index cd50143fc..93e327bbb 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -16,7 +16,7 @@ contract Staking is IStaking, Initializable { /// @dev Validator array. The order of the validator is assured not to be changed, since this /// array is kept synced with the array in the `Staking` contract. ValidatorCandidate[] public validatorCandidates; - + /// @dev Index of validators that are on renounce uint256[] public pendingRenoucingValidatorIndexes; @@ -83,17 +83,19 @@ contract Staking is IStaking, Initializable { require(_treasuryAddr != address(0), "Staking: invalid treasury address"); ValidatorCandidate storage _candidate; - if (_validatorIndexes[_consensusAddr] > 0) { /// renounced validator joins as a validator again + if (_validatorIndexes[_consensusAddr] > 0) { + /// renounced validator joins as a validator again (, _candidate) = _getDeprecatedCandidate(_consensusAddr); require(_candidate.state == ValidatorState.RENOUNCED, "Staking: cannot propose an existed candidate"); - } else { /// totally new validator joins + } else { + /// totally new validator joins _candidateIdx = validatorCandidates.length; _candidate = validatorCandidates.push(); _validatorIndexes[_consensusAddr] = _candidateIdx; _candidate.consensusAddr = _consensusAddr; } - _candidate.stakingAddr = _stakingAddr; + _candidate.candidateAdmin = _stakingAddr; _candidate.treasuryAddr = _treasuryAddr; _candidate.commissionRate = _commissionRate; _candidate.stakedAmount = _amount; @@ -108,9 +110,9 @@ contract Staking is IStaking, Initializable { uint256 _amount = msg.value; (, ValidatorCandidate storage _candidate) = _getCandidate(_consensusAddr); console.log("[*] Staking"); - console.log("[ ] \t stake address:\t", _candidate.stakingAddr); + console.log("[ ] \t stake address:\t", _candidate.candidateAdmin); console.log("[ ] \t consensus address:\t", _candidate.consensusAddr); - require(_candidate.stakingAddr == msg.sender, "Staking: invalid staking address"); + require(_candidate.candidateAdmin == msg.sender, "Staking: invalid staking address"); _candidate.stakedAmount += _amount; emit Staked(_consensusAddr, _amount); @@ -121,7 +123,7 @@ contract Staking is IStaking, Initializable { */ function unstake(address _consensusAddr, uint256 _amount) external { (, ValidatorCandidate storage _candidate) = _getCandidate(_consensusAddr); - require(_candidate.stakingAddr == msg.sender, "Staking: caller must be staking address"); + require(_candidate.candidateAdmin == msg.sender, "Staking: caller must be staking address"); uint256 _maxUnstake = _candidate.stakedAmount - minValidatorBalance; require(_amount <= _maxUnstake, "Staking: invalid staked amount left"); @@ -133,32 +135,32 @@ contract Staking is IStaking, Initializable { /** * @notice Allow validator sends renouncing request. The request gets affected when the epoch ends - * + * * @dev The following procedure must be done in multiple methods in order to finish the renounce. - * + * * 1. This method: - * - Set `ValidatorState` of validator to `ON_RENOUNCE`; + * - Set `ValidatorState` of validator to `ON_RENOUNCE`; * - Push the validator to a pending list; * - Trigger the `stateChanged` flag. - * + * * 2. The `updateValidatorSet` method: * - Set the balance-to-sort of the validator to `0`; * - Reset the `stateChanged`s flag. - * + * * 3. The `finalizeRenouncingValidator` method: * - Remove validator from pending list * - Set `ValidatorState` of validator to `RENOUNCED`; * - Set `stakedAmount` of validator to `0`; * - Transfer the staked to the respective staking address. - * + * * Requirements: * - The validator must be exist * - The validator must be in `ACTIVE` state - * + * */ function requestRenouncingValidator(address _consensusAddr) external { (uint256 _index, ValidatorCandidate storage _candidate) = _getCandidate(_consensusAddr); - require(_candidate.stakingAddr == msg.sender, "Staking: caller must be staking address"); + require(_candidate.candidateAdmin == msg.sender, "Staking: caller must be staking address"); stateChanged = true; _candidate.state = ValidatorState.ON_RENOUNCE; @@ -169,7 +171,7 @@ contract Staking is IStaking, Initializable { function finalizeRenouncingValidator(address _consensusAddr) external { (uint256 _index, ValidatorCandidate storage _candidate) = _getDeprecatedCandidate(_consensusAddr); - require(_candidate.stakingAddr == msg.sender, "Staking: caller must be staking address"); + require(_candidate.candidateAdmin == msg.sender, "Staking: caller must be staking address"); require(_candidate.state == ValidatorState.ON_RENOUNCE, "Staking: validator state is not ON_RENOUNCE"); bool _found; @@ -247,26 +249,6 @@ contract Staking is IStaking, Initializable { revert("Unimplemented"); } - function onDeposit() external payable override { - revert("Unimplemented"); - } - - function allocateReward(address valAddr, uint256 amount) external override { - revert("Unimplemented"); - } - - function receiveReward(address valAddr) external payable override { - revert("Unimplemented"); - } - - function claimReward(uint256 amount) external override { - revert("Unimplemented"); - } - - function slash(address valAddr, uint256 amount) external override { - revert("Unimplemented"); - } - /** * @dev Delegates from a validator address. * @@ -355,7 +337,7 @@ contract Staking is IStaking, Initializable { // Using `call` to remove 2300 gas stipend // https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/ (bool _success, ) = _to.call{ value: _amount }(""); - require(_success, "Staking: transfer RON failed"); + require(_success, "Staking: transfer RON failed"); } /** @@ -413,4 +395,33 @@ contract Staking is IStaking, Initializable { return currentValidatorSet_; } + + function governanceAdminContract() external view override returns (address) {} + + function setMinValidatorBalance(uint256) external override {} + + function maxValidatorCandidate() external view override returns (uint256) {} + + function setMaxValidatorCandidate(uint256) external override {} + + function getValidatorCandidates() external view override returns (ValidatorCandidate[] memory candidates) {} + + function recordReward(address _consensusAddr, uint256 _reward) external override {} + + function settleRewardPool(address _consensusAddr) external override {} + + function onValidatorSlashed(address _consensusAddr) external override {} + + function deductStakingAmount(address _consensusAddr, uint256 _amount) external override {} + + function renounce(address consensusAddr) external override {} + + function getRewards(address _user, address[] calldata _poolAddrList) + external + view + override + returns (uint256[] memory _pendings, uint256[] memory _claimables) + {} + + function claimRewards(address[] calldata _consensusAddrList) external override returns (uint256 _amount) {} } From 1d93c3a3331613d776b1006d13efafa871a5b1eb Mon Sep 17 00:00:00 2001 From: nxqbao Date: Sun, 4 Sep 2022 21:18:16 +0700 Subject: [PATCH 045/190] [Staking] Fix renouncing function --- contracts/interfaces/IStaking.sol | 3 +- contracts/staking/Staking.sol | 80 ++++++++++++++++++++++++------- test/staking/Staking.test.ts | 19 ++++++-- 3 files changed, 82 insertions(+), 20 deletions(-) diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 897de1ed3..1ca20a1d8 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -5,7 +5,8 @@ pragma solidity ^0.8.9; interface IStaking { enum ValidatorState { ACTIVE, - ON_RENOUNCE, + ON_REQUESTING_RENOUNCE, + ON_CONFIRMED_RENOUNCE, RENOUNCED } diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 93e327bbb..f11e6dd62 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -18,7 +18,7 @@ contract Staking is IStaking, Initializable { ValidatorCandidate[] public validatorCandidates; /// @dev Index of validators that are on renounce - uint256[] public pendingRenoucingValidatorIndexes; + uint256[] internal _pendingRenoucingValidatorIndexes; /// @dev Mapping from delegator address => consensus address => delegated amount. mapping(address => mapping(address => uint256)) delegatedAmount; @@ -67,6 +67,19 @@ contract Staking is IStaking, Initializable { validatorCandidates.push(); } + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR GOVERNANCE // + /////////////////////////////////////////////////////////////////////////////////////// + + // TODO: restrict to only govenance + function setValidatorSetContract (IValidatorSet _validatorSetContract) external { + validatorSetContract = _validatorSetContract; + } + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR VALIDATORS // + /////////////////////////////////////////////////////////////////////////////////////// + /** * @dev See {IStaking-proposeValidator}. */ @@ -134,16 +147,17 @@ contract Staking is IStaking, Initializable { } /** - * @notice Allow validator sends renouncing request. The request gets affected when the epoch ends + * @notice Allow validator sends renouncing request. The request gets affected when the epoch ends. * * @dev The following procedure must be done in multiple methods in order to finish the renounce. * * 1. This method: - * - Set `ValidatorState` of validator to `ON_RENOUNCE`; + * - Set `ValidatorState` of validator to `ON_REQUESTING_RENOUNCE`; * - Push the validator to a pending list; * - Trigger the `stateChanged` flag. * * 2. The `updateValidatorSet` method: + * - Set `ValidatorState` of validator to `ON_CONFIRMED_RENOUNCE`; * - Set the balance-to-sort of the validator to `0`; * - Reset the `stateChanged`s flag. * @@ -162,26 +176,41 @@ contract Staking is IStaking, Initializable { (uint256 _index, ValidatorCandidate storage _candidate) = _getCandidate(_consensusAddr); require(_candidate.candidateAdmin == msg.sender, "Staking: caller must be staking address"); + console.log("[*] requestRenouncingValidator"); + console.log("[ ] \t index", _index); + stateChanged = true; - _candidate.state = ValidatorState.ON_RENOUNCE; - pendingRenoucingValidatorIndexes.push(_index); + _candidate.state = ValidatorState.ON_REQUESTING_RENOUNCE; + _pendingRenoucingValidatorIndexes.push(_index); emit ValidatorRenounceRequested(_consensusAddr, _candidate.stakedAmount); } + /** + * @notice Allow validator finalizes renouncing request. + * + * @dev For the logic of this method, refer to {IStaking-requestRenouncingValidator} + * + * Requirements: + * - The validator must be exist + * - The validator must submitted renouncing request, and be `ON_CONFIRMED_RENOUNCE` state + */ function finalizeRenouncingValidator(address _consensusAddr) external { (uint256 _index, ValidatorCandidate storage _candidate) = _getDeprecatedCandidate(_consensusAddr); require(_candidate.candidateAdmin == msg.sender, "Staking: caller must be staking address"); - require(_candidate.state == ValidatorState.ON_RENOUNCE, "Staking: validator state is not ON_RENOUNCE"); + require( + _candidate.state == ValidatorState.ON_CONFIRMED_RENOUNCE, + "Staking: validator state is not ON_CONFIRMED_RENOUNCE" + ); bool _found; - uint _length = pendingRenoucingValidatorIndexes.length; + uint _length = _pendingRenoucingValidatorIndexes.length; uint _amount = _candidate.stakedAmount; for (uint i; i < _length; ++i) { - if (_index == pendingRenoucingValidatorIndexes[i]) { + if (_index == _pendingRenoucingValidatorIndexes[i]) { _found = true; - pendingRenoucingValidatorIndexes[i] = pendingRenoucingValidatorIndexes[_length - 1]; - pendingRenoucingValidatorIndexes.pop(); + _pendingRenoucingValidatorIndexes[i] = _pendingRenoucingValidatorIndexes[_length - 1]; + _pendingRenoucingValidatorIndexes.pop(); break; } } @@ -347,20 +376,33 @@ contract Staking is IStaking, Initializable { * assigned to the new set. The result is returned to the `ValidatorSet` contract. * * Requirements: - * - Only validator and `ValidatorSet` contract can call this function + * - Only `ValidatorSet` contract can call this function * * @return newValidatorSet Validator set for the new epoch */ - function updateValidatorSet() external returns (ValidatorCandidate[] memory newValidatorSet) { + function updateValidatorSet() + external + onlyValidatorSetContract + returns (ValidatorCandidate[] memory newValidatorSet) + { + /// checking global state, skipping sorting if unchanged console.log("[*] updateValidatorSet"); if (!stateChanged) { - console.log("[ ] skipped"); + console.log("[ ] \t skipped"); return getCurrentValidatorSet(); } - console.log("[ ] sorted"); + console.log("[ ] \t sorted"); stateChanged = false; + /// update renouncing status + for (uint i = 0; i < _pendingRenoucingValidatorIndexes.length; ++i) { + console.log("[ ] \t updating renouncing", i, _pendingRenoucingValidatorIndexes[i]); + ValidatorCandidate storage _renouncingValidator = validatorCandidates[_pendingRenoucingValidatorIndexes[i]]; + _renouncingValidator.state = ValidatorState.ON_CONFIRMED_RENOUNCE; + } + + /// prepare sorting data uint _length = validatorCandidates.length; Sorting.Node[] memory _nodes = new Sorting.Node[](_length); Sorting.Node[] memory _sortedNodes = new Sorting.Node[](_length); @@ -388,12 +430,18 @@ contract Staking is IStaking, Initializable { function getCurrentValidatorSet() public view returns (ValidatorCandidate[] memory currentValidatorSet_) { uint _length = currentValidatorIndexes.length; - currentValidatorSet_ = new ValidatorCandidate[](currentValidatorIndexes.length); + currentValidatorSet_ = new ValidatorCandidate[](_length); for (uint i = 0; i < _length; i++) { currentValidatorSet_[i] = validatorCandidates[currentValidatorIndexes[i]]; } + } - return currentValidatorSet_; + function getPendingRenoucingValidatorIndexes() public view returns (uint[] memory pendingRenouncingIndexes_) { + uint _length = _pendingRenoucingValidatorIndexes.length; + pendingRenouncingIndexes_ = new uint[](_length); + for (uint i = 0; i < _length; i++) { + pendingRenouncingIndexes_[i] = _pendingRenoucingValidatorIndexes[i]; + } } function governanceAdminContract() external view override returns (address) {} diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index 26c190cfb..76f425b05 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -32,7 +32,7 @@ const generateCandidate = ( }; }; -describe('PoS Staking test', () => { +describe('Staking test', () => { let unstakingOnHoldBlocksNum: BigNumber; let minValidatorBalance: BigNumber; @@ -51,6 +51,9 @@ describe('PoS Staking test', () => { unstakingOnHoldBlocksNum = await stakingContract.unstakingOnHoldBlocksNum(); minValidatorBalance = await stakingContract.minValidatorBalance(); + console.log('Set validator set contract...'); + await stakingContract.setValidatorSetContract(admin.address); + console.log('Init addresses...'); for (let i = 0; i < 10; i++) { candidates.push( @@ -206,7 +209,15 @@ describe('PoS Staking test', () => { await expect(tx).to.revertedWith('Staking: caller must be staking address'); }); + it('Should not be able to finalize renounce - renounce is not confirmed', async () => { + let tx = stakingContract.connect(stakingAddrs[0]).finalizeRenouncingValidator(consensusAddrs[0].address); + await expect(tx).to.revertedWith('Staking: validator state is not ON_CONFIRMED_RENOUNCE'); + }); + it('Should be able to finalize the renounce', async () => { + expect(await stakingContract.getPendingRenoucingValidatorIndexes()).to.eql([BigNumber.from(1)]); + await stakingContract.connect(admin).updateValidatorSet(); + let tx; let incValue = minValidatorBalance.add(ethers.utils.parseEther('1')); let decValue = BigNumber.from(0).sub(incValue); @@ -216,11 +227,13 @@ describe('PoS Staking test', () => { await expect(tx) .to.emit(stakingContract, 'ValidatorRenounceFinalized') .withArgs(consensusAddrs[0].address, incValue); + + expect(await stakingContract.getPendingRenoucingValidatorIndexes()).to.eql([]); }); it('Should not be able to finalize renounce twice', async () => { let tx = stakingContract.connect(stakingAddrs[0]).finalizeRenouncingValidator(consensusAddrs[0].address); - await expect(tx).to.revertedWith('Staking: validator state is not ON_RENOUNCE'); + await expect(tx).to.revertedWith('Staking: validator state is not ON_CONFIRMED_RENOUNCE'); }); it('Should not be able to stake for a renounced validator', async () => { @@ -239,6 +252,6 @@ describe('PoS Staking test', () => { describe('Delegator functions', async () => {}); - describe('Updating validator fucntions', async () => {}); + describe('Updating validator functions', async () => {}); }); }); From ac3d47d8ddbc9990902cbe276ccb59a20ec67f4e Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 5 Sep 2022 00:13:45 +0700 Subject: [PATCH 046/190] [Staking] Fix updateValidatorSet --- contracts/staking/Staking.sol | 94 +++++++++++++++++++--------- test/staking/Staking.test.ts | 113 +++++++++++++++++++++++++++++++--- 2 files changed, 170 insertions(+), 37 deletions(-) diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index f11e6dd62..e2ff8e571 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -17,8 +17,12 @@ contract Staking is IStaking, Initializable { /// array is kept synced with the array in the `Staking` contract. ValidatorCandidate[] public validatorCandidates; + /// @dev Index of validators that are mining in the current epoch. Get updated each epoch. + /// Element at 0-index always is `0`. + uint256[] internal _currentValidatorIndexes; + /// @dev Index of validators that are on renounce - uint256[] internal _pendingRenoucingValidatorIndexes; + uint256[] internal _pendingRenouncingValidatorIndexes; /// @dev Mapping from delegator address => consensus address => delegated amount. mapping(address => mapping(address => uint256)) delegatedAmount; @@ -36,15 +40,15 @@ contract Staking is IStaking, Initializable { /// @dev Configuration of minimum balance for being a validator uint256 public minValidatorBalance; - uint256[] internal currentValidatorIndexes; + /// @dev Number of maximum working validator in one epoch + uint256 public numOfCabinets; - IValidatorSet validatorSetContract; + /// @dev Validator contract address + IValidatorSet public validatorSetContract; /// @dev Helper global flag which is set to true on at least one validator balance changes, and /// is reset to false per epoch. This help reduces redundant sortings. - bool stateChanged; - - uint256 public numOfCabinets; + bool internal _globalBalanceChanged; modifier onlyValidatorSetContract() { require(msg.sender == address(validatorSetContract), "Only validator set contract"); @@ -59,7 +63,8 @@ contract Staking is IStaking, Initializable { * @dev Initializes the contract storage. */ function initialize() external initializer { - /// TODO: bring this constant variable to params. + /// TODO: bring these constant variable to params. + numOfCabinets = 21; totalValidatorThreshold = 50; unstakingOnHoldBlocksNum = 28800; // 28800 blocks ~= 1 day minValidatorBalance = 3 * 1e6 * 1e18; // 3m RON @@ -72,9 +77,14 @@ contract Staking is IStaking, Initializable { /////////////////////////////////////////////////////////////////////////////////////// // TODO: restrict to only govenance - function setValidatorSetContract (IValidatorSet _validatorSetContract) external { + function setValidatorSetContract(IValidatorSet _validatorSetContract) external { validatorSetContract = _validatorSetContract; - } + } + + // TODO: restrict to only govenance + function setNumOfCabinets(uint256 _numOfCabinets) external { + numOfCabinets = _numOfCabinets; + } /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR VALIDATORS // @@ -100,6 +110,7 @@ contract Staking is IStaking, Initializable { /// renounced validator joins as a validator again (, _candidate) = _getDeprecatedCandidate(_consensusAddr); require(_candidate.state == ValidatorState.RENOUNCED, "Staking: cannot propose an existed candidate"); + _candidate.state = ValidatorState.ACTIVE; } else { /// totally new validator joins _candidateIdx = validatorCandidates.length; @@ -113,6 +124,8 @@ contract Staking is IStaking, Initializable { _candidate.commissionRate = _commissionRate; _candidate.stakedAmount = _amount; + _globalBalanceChanged = true; + emit ValidatorProposed(_consensusAddr, _stakingAddr, _amount, _candidate); } @@ -128,6 +141,9 @@ contract Staking is IStaking, Initializable { require(_candidate.candidateAdmin == msg.sender, "Staking: invalid staking address"); _candidate.stakedAmount += _amount; + + _globalBalanceChanged = true; + emit Staked(_consensusAddr, _amount); } @@ -143,6 +159,8 @@ contract Staking is IStaking, Initializable { _candidate.stakedAmount -= _amount; _transferRON(msg.sender, _amount); + _globalBalanceChanged = true; + emit Unstaked(_consensusAddr, _amount); } @@ -154,12 +172,12 @@ contract Staking is IStaking, Initializable { * 1. This method: * - Set `ValidatorState` of validator to `ON_REQUESTING_RENOUNCE`; * - Push the validator to a pending list; - * - Trigger the `stateChanged` flag. + * - Trigger the `_globalBalanceChanged` flag. * * 2. The `updateValidatorSet` method: * - Set `ValidatorState` of validator to `ON_CONFIRMED_RENOUNCE`; * - Set the balance-to-sort of the validator to `0`; - * - Reset the `stateChanged`s flag. + * - Reset the `_globalBalanceChanged`s flag. * * 3. The `finalizeRenouncingValidator` method: * - Remove validator from pending list @@ -179,9 +197,10 @@ contract Staking is IStaking, Initializable { console.log("[*] requestRenouncingValidator"); console.log("[ ] \t index", _index); - stateChanged = true; _candidate.state = ValidatorState.ON_REQUESTING_RENOUNCE; - _pendingRenoucingValidatorIndexes.push(_index); + _pendingRenouncingValidatorIndexes.push(_index); + + _globalBalanceChanged = true; emit ValidatorRenounceRequested(_consensusAddr, _candidate.stakedAmount); } @@ -204,13 +223,13 @@ contract Staking is IStaking, Initializable { ); bool _found; - uint _length = _pendingRenoucingValidatorIndexes.length; + uint _length = _pendingRenouncingValidatorIndexes.length; uint _amount = _candidate.stakedAmount; for (uint i; i < _length; ++i) { - if (_index == _pendingRenoucingValidatorIndexes[i]) { + if (_index == _pendingRenouncingValidatorIndexes[i]) { _found = true; - _pendingRenoucingValidatorIndexes[i] = _pendingRenoucingValidatorIndexes[_length - 1]; - _pendingRenoucingValidatorIndexes.pop(); + _pendingRenouncingValidatorIndexes[i] = _pendingRenouncingValidatorIndexes[_length - 1]; + _pendingRenouncingValidatorIndexes.pop(); break; } } @@ -387,18 +406,18 @@ contract Staking is IStaking, Initializable { { /// checking global state, skipping sorting if unchanged console.log("[*] updateValidatorSet"); - if (!stateChanged) { + if (!_globalBalanceChanged) { console.log("[ ] \t skipped"); return getCurrentValidatorSet(); } console.log("[ ] \t sorted"); - stateChanged = false; + _globalBalanceChanged = false; /// update renouncing status - for (uint i = 0; i < _pendingRenoucingValidatorIndexes.length; ++i) { - console.log("[ ] \t updating renouncing", i, _pendingRenoucingValidatorIndexes[i]); - ValidatorCandidate storage _renouncingValidator = validatorCandidates[_pendingRenoucingValidatorIndexes[i]]; + for (uint i = 0; i < _pendingRenouncingValidatorIndexes.length; ++i) { + console.log("[ ] \t updating renouncing", i, _pendingRenouncingValidatorIndexes[i]); + ValidatorCandidate storage _renouncingValidator = validatorCandidates[_pendingRenouncingValidatorIndexes[i]]; _renouncingValidator.state = ValidatorState.ON_CONFIRMED_RENOUNCE; } @@ -407,6 +426,7 @@ contract Staking is IStaking, Initializable { Sorting.Node[] memory _nodes = new Sorting.Node[](_length); Sorting.Node[] memory _sortedNodes = new Sorting.Node[](_length); + console.log("[ ] \t prepare data"); for (uint i = 0; i < _length; i++) { ValidatorCandidate memory _validator = validatorCandidates[i]; @@ -414,33 +434,47 @@ contract Staking is IStaking, Initializable { _nodes[i].value = (_validator.state == ValidatorState.ACTIVE) ? _validator.stakedAmount + _validator.delegatedAmount : 0; + + console.log("[ ] \t\t key, value", _nodes[i].key, _nodes[i].value); } - delete currentValidatorIndexes; + delete _currentValidatorIndexes; _sortedNodes = Sorting.sortNodes(_nodes); /// TODO: pick M validators which are governance - uint _currentSetSize = _length < numOfCabinets ? _length : numOfCabinets; + uint _currentSetSize = (_length < numOfCabinets) ? _length : numOfCabinets; + console.log("[ ] \t after sort"); + console.log("[ ] \t\t _currentSetSize", _currentSetSize); for (uint i = 0; i < _currentSetSize; i++) { - currentValidatorIndexes.push(_sortedNodes[i].value); + console.log("[ ] \t\t key, value", _sortedNodes[i].key, _sortedNodes[i].value); + _currentValidatorIndexes.push(_sortedNodes[i].key); } return getCurrentValidatorSet(); } + + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR QUERYING // + /////////////////////////////////////////////////////////////////////////////////////// + function getCurrentValidatorSet() public view returns (ValidatorCandidate[] memory currentValidatorSet_) { - uint _length = currentValidatorIndexes.length; + console.log("[*] getPendingRenouncingValidatorIndexes"); + uint _length = _currentValidatorIndexes.length; currentValidatorSet_ = new ValidatorCandidate[](_length); for (uint i = 0; i < _length; i++) { - currentValidatorSet_[i] = validatorCandidates[currentValidatorIndexes[i]]; + console.log("[ ] \t _i, _currentIndexes[i]", i, _currentValidatorIndexes[i]); + currentValidatorSet_[i] = validatorCandidates[_currentValidatorIndexes[i]]; } } - function getPendingRenoucingValidatorIndexes() public view returns (uint[] memory pendingRenouncingIndexes_) { - uint _length = _pendingRenoucingValidatorIndexes.length; + + function getPendingRenouncingValidatorIndexes() public view returns (uint[] memory pendingRenouncingIndexes_) { + uint _length = _pendingRenouncingValidatorIndexes.length; pendingRenouncingIndexes_ = new uint[](_length); for (uint i = 0; i < _length; i++) { - pendingRenouncingIndexes_[i] = _pendingRenoucingValidatorIndexes[i]; + pendingRenouncingIndexes_[i] = _pendingRenouncingValidatorIndexes[i]; } } diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index 76f425b05..f20050fdc 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -3,7 +3,7 @@ import { ethers, deployments } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { Staking, Staking__factory } from '../../src/types'; -import { ValidatorCandidateStruct } from '../../src/types/ValidatorSetCoreMock'; +import { ValidatorCandidateStruct } from '../../src/types/IStaking'; import { BigNumber } from 'ethers'; let stakingContract: Staking; @@ -15,23 +15,47 @@ let stakingAddrs: SignerWithAddress[]; let consensusAddrs: SignerWithAddress[]; let treasuryAddrs: SignerWithAddress[]; +enum ValidatorStateEnum { + ACTIVE = 0, + ON_REQUESTING_RENOUNCE = 1, + ON_CONFIRMED_RENOUNCE = 2, + RENOUNCED = 3, +} + const generateCandidate = ( - stakingAddr: string, + candidateAdmin: string, consensusAddr: string, treasuryAddr: string ): ValidatorCandidateStruct => { return { - stakingAddr: stakingAddr, + candidateAdmin: candidateAdmin, consensusAddr: consensusAddr, treasuryAddr: treasuryAddr, commissionRate: 0, stakedAmount: 0, + state: ValidatorStateEnum.ACTIVE, delegatedAmount: 0, governing: false, ____gap: Array.apply(null, Array(20)).map((_) => 0), }; }; +const validateTwoObjects = async (definedObj: any, resultObj: any) => { + let key: keyof typeof definedObj; + for (key in definedObj) { + const definedVal = definedObj[key]; + const resultVal = resultObj[key]; + + if (Array.isArray(resultVal)) { + for (let i = 0; i < resultVal.length; i++) { + await expect(resultVal[i]).to.eq(definedVal[i]); + } + } else { + await expect(resultVal).to.eq(definedVal); + } + } +}; + describe('Staking test', () => { let unstakingOnHoldBlocksNum: BigNumber; let minValidatorBalance: BigNumber; @@ -54,7 +78,7 @@ describe('Staking test', () => { console.log('Set validator set contract...'); await stakingContract.setValidatorSetContract(admin.address); - console.log('Init addresses...'); + console.log('Init addresses for 10 candidates...'); for (let i = 0; i < 10; i++) { candidates.push( generateCandidate(signers[3 * i].address, signers[3 * i + 1].address, signers[3 * i + 2].address) @@ -215,7 +239,7 @@ describe('Staking test', () => { }); it('Should be able to finalize the renounce', async () => { - expect(await stakingContract.getPendingRenoucingValidatorIndexes()).to.eql([BigNumber.from(1)]); + expect(await stakingContract.getPendingRenouncingValidatorIndexes()).to.eql([BigNumber.from(1)]); await stakingContract.connect(admin).updateValidatorSet(); let tx; @@ -228,7 +252,7 @@ describe('Staking test', () => { .to.emit(stakingContract, 'ValidatorRenounceFinalized') .withArgs(consensusAddrs[0].address, incValue); - expect(await stakingContract.getPendingRenoucingValidatorIndexes()).to.eql([]); + expect(await stakingContract.getPendingRenouncingValidatorIndexes()).to.eql([]); }); it('Should not be able to finalize renounce twice', async () => { @@ -243,10 +267,85 @@ describe('Staking test', () => { }); await expect(tx).to.revertedWith('Staking: query for deprecated candidate'); }); + + it('Should be able to re-propose the renounced validator, the old index unchanges', async () => { + let tx = await stakingContract + .connect(stakingAddrs[0]) + .proposeValidator(consensusAddrs[0].address, treasuryAddrs[0].address, 0, { + value: minValidatorBalance.toString(), + }); + expect(await tx) + .to.emit(stakingContract, 'ValidatorProposed') + .withArgs(consensusAddrs[0].address, stakingAddrs[0].address, minValidatorBalance, candidates[0]); + + let _expectingCandidate = { + candidateAdmin: stakingAddrs[0].address, + consensusAddr: consensusAddrs[0].address, + treasuryAddr: treasuryAddrs[0].address, + commissionRate: 0, + stakedAmount: minValidatorBalance, + state: ValidatorStateEnum.ACTIVE, + delegatedAmount: 0, + governing: false, + }; + await validateTwoObjects(_expectingCandidate, await stakingContract.validatorCandidates(1)); + }); }); describe('Propose and renounce multiple validator', async () => { - // TODO: + it('Should be able to propose 5 more validator', async () => { + // balance: [3M, 3M+1, 3M+2, 3M+3, 3M+4, 3M+5] + for (let i = 1; i <= 5; ++i) { + let topupValue = minValidatorBalance.add(ethers.utils.parseEther(i.toString())); + let tx = await stakingContract + .connect(stakingAddrs[i]) + .proposeValidator(consensusAddrs[i].address, treasuryAddrs[i].address, i * 100, { + value: topupValue, + }); + expect(await tx) + .to.emit(stakingContract, 'ValidatorProposed') + .withArgs(consensusAddrs[i].address, stakingAddrs[i].address, topupValue, candidates[i]); + } + + await stakingContract.connect(admin).updateValidatorSet(); + let currentSet = await stakingContract.getCurrentValidatorSet(); + console.log( + '>>> currentSet', + currentSet.map((x) => { + return { consensusAddrs: x.consensusAddr, amount: x.stakedAmount }; + }) + ); + }); + + describe('Renounce 2 in 6 validator', async () => { + it('Should request to renounce success', async () => { + for (let i = 3; i <= 4; ++i) { + let topoutValue = minValidatorBalance.add(ethers.utils.parseEther(i.toString())); + let tx = stakingContract.connect(stakingAddrs[i]).requestRenouncingValidator(consensusAddrs[i].address); + await expect(tx) + .to.emit(stakingContract, 'ValidatorRenounceRequested') + .withArgs(consensusAddrs[i].address, topoutValue); + } + }); + + it('Should pending list get updated correctly', async () => { + expect(await stakingContract.getPendingRenouncingValidatorIndexes()).to.eql([ + BigNumber.from(4), + BigNumber.from(5), + ]); + }); + + it('Should be update validators list success', async () => { + await stakingContract.connect(admin).updateValidatorSet(); + let currentSet = await stakingContract.getCurrentValidatorSet(); + console.log( + '>>> currentSet', + currentSet.map((x) => { + return { consensusAddrs: x.consensusAddr, amount: x.stakedAmount }; + }) + ); + }); + }); }); }); From 6fbed874b66ce98debced60ccde47fdfcd118ba3 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 5 Sep 2022 01:30:24 +0700 Subject: [PATCH 047/190] [Sorting] Fix quick sort implementation --- contracts/libraries/Sorting.sol | 39 ++++++++++++++++++++++----------- contracts/staking/Staking.sol | 11 ++++++---- test/staking/Staking.test.ts | 13 +++++++---- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/contracts/libraries/Sorting.sol b/contracts/libraries/Sorting.sol index 12b4827e9..db64b1e02 100644 --- a/contracts/libraries/Sorting.sol +++ b/contracts/libraries/Sorting.sol @@ -8,11 +8,12 @@ library Sorting { uint value; } - function sort(uint[] memory data) public view returns (uint[] memory) { + function sort(uint[] memory data) public pure returns (uint[] memory) { return _quickSort(data, int(0), int(data.length - 1)); } - function sortNodes(Node[] memory nodes) public view returns (Node[] memory) { + function sortNodes(Node[] memory nodes) public pure returns (Node[] memory) { + // return _bubbleSortNodes(nodes); return _quickSortNodes(nodes, int(0), int(nodes.length - 1)); } @@ -20,22 +21,22 @@ library Sorting { uint[] memory arr, int left, int right - ) private view returns (uint[] memory) { + ) private pure returns (uint[] memory) { int i = left; int j = right; if (i == j) return arr; uint pivot = arr[uint(left + (right - left) / 2)]; while (i <= j) { - while (arr[uint(i)] < pivot) i++; - while (pivot < arr[uint(j)]) j--; + while (arr[uint(i)] > pivot) i++; + while (pivot > arr[uint(j)]) j--; if (i <= j) { (arr[uint(i)], arr[uint(j)]) = (arr[uint(j)], arr[uint(i)]); i++; j--; } } - if (left < j) _quickSort(arr, left, j); - if (i < right) _quickSort(arr, i, right); + if (left < j) arr = _quickSort(arr, left, j); + if (i < right) arr = _quickSort(arr, i, right); return arr; } @@ -44,28 +45,40 @@ library Sorting { Node[] memory nodes, int left, int right - ) private view returns (Node[] memory) { + ) private pure returns (Node[] memory) { int i = left; int j = right; if (i == j) return nodes; Node memory pivot = nodes[uint(left + (right - left) / 2)]; while (i <= j) { - while (nodes[uint(i)].value < pivot.value) i++; - while (pivot.value < nodes[uint(j)].value) j--; + while (nodes[uint(i)].value > pivot.value) i++; + while (pivot.value > nodes[uint(j)].value) j--; if (i <= j) { (nodes[uint(i)], nodes[uint(j)]) = __swapNodes(nodes[uint(i)], nodes[uint(j)]); i++; j--; } } - if (left < j) _quickSortNodes(nodes, left, j); - if (i < right) _quickSortNodes(nodes, i, right); + if (left < j) nodes = _quickSortNodes(nodes, left, j); + if (i < right) nodes = _quickSortNodes(nodes, i, right); return nodes; } + function _bubbleSortNodes(Node[] memory nodes) private pure returns (Node[] memory) { + uint length = nodes.length; + for (uint i = 0; i < length - 1; i++) { + for (uint j = i + 1; j < length; j++) { + if (nodes[j].value > nodes[i].value) { + (nodes[i], nodes[j]) = __swapNodes(nodes[i], nodes[j]); + } + } + } + return nodes; + } + function __swapNodes(Node memory x, Node memory y) private pure returns (Node memory, Node memory) { - Node memory tmp; + Node memory tmp = x; (x, y) = (y, tmp); return (x, y); } diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index e2ff8e571..3a54a4b5c 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -14,11 +14,12 @@ contract Staking is IStaking, Initializable { mapping(address => uint256) internal _validatorIndexes; /// TODO: expose fn returns the whole validator array. /// @dev Validator array. The order of the validator is assured not to be changed, since this - /// array is kept synced with the array in the `Staking` contract. + /// array is kept synced with the array in the `Staking` contract. The element at 0-index is + /// always an empty validator. ValidatorCandidate[] public validatorCandidates; /// @dev Index of validators that are mining in the current epoch. Get updated each epoch. - /// Element at 0-index always is `0`. + /// Element at 0-index is always `0`. uint256[] internal _currentValidatorIndexes; /// @dev Index of validators that are on renounce @@ -422,12 +423,14 @@ contract Staking is IStaking, Initializable { } /// prepare sorting data + /// TODO(bao): only take active validators to sort uint _length = validatorCandidates.length; Sorting.Node[] memory _nodes = new Sorting.Node[](_length); Sorting.Node[] memory _sortedNodes = new Sorting.Node[](_length); console.log("[ ] \t prepare data"); - for (uint i = 0; i < _length; i++) { + _nodes[0] = Sorting.Node(0, type(uint256).max); + for (uint i = 1; i < _length; i++) { ValidatorCandidate memory _validator = validatorCandidates[i]; _nodes[i].key = i; @@ -441,7 +444,7 @@ contract Staking is IStaking, Initializable { delete _currentValidatorIndexes; _sortedNodes = Sorting.sortNodes(_nodes); - /// TODO: pick M validators which are governance + /// TODO(bao): pick M validators which are governance uint _currentSetSize = (_length < numOfCabinets) ? _length : numOfCabinets; console.log("[ ] \t after sort"); console.log("[ ] \t\t _currentSetSize", _currentSetSize); diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index f20050fdc..997f6c5b7 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -5,6 +5,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { Staking, Staking__factory } from '../../src/types'; import { ValidatorCandidateStruct } from '../../src/types/IStaking'; import { BigNumber } from 'ethers'; +import { DEFAULT_ADDRESS } from '../../src/utils'; let stakingContract: Staking; @@ -308,13 +309,15 @@ describe('Staking test', () => { } await stakingContract.connect(admin).updateValidatorSet(); - let currentSet = await stakingContract.getCurrentValidatorSet(); + let currentSet = (await stakingContract.getCurrentValidatorSet()).map((x) => x.consensusAddr); + let expectingSet = [DEFAULT_ADDRESS].concat([5, 4, 3, 2, 1, 0].map((i) => consensusAddrs[i].address)); console.log( '>>> currentSet', - currentSet.map((x) => { + (await stakingContract.getCurrentValidatorSet()).map((x) => { return { consensusAddrs: x.consensusAddr, amount: x.stakedAmount }; }) ); + await expect(expectingSet).eql(currentSet); }); describe('Renounce 2 in 6 validator', async () => { @@ -337,13 +340,15 @@ describe('Staking test', () => { it('Should be update validators list success', async () => { await stakingContract.connect(admin).updateValidatorSet(); - let currentSet = await stakingContract.getCurrentValidatorSet(); + let currentSet = (await stakingContract.getCurrentValidatorSet()).map((x) => x.consensusAddr); + let expectingSet = [DEFAULT_ADDRESS].concat([5, 2, 1, 0].map((i) => consensusAddrs[i].address)); console.log( '>>> currentSet', - currentSet.map((x) => { + (await stakingContract.getCurrentValidatorSet()).map((x) => { return { consensusAddrs: x.consensusAddr, amount: x.stakedAmount }; }) ); + await expect(expectingSet).eql(currentSet); }); }); }); From 6b1f12fac75578974b9924b6c7ead1fedfaa5a14 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 5 Sep 2022 01:30:37 +0700 Subject: [PATCH 048/190] Ignore remix config file --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 906949bb6..5e8e3b42f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ yarn-error.log .idea refs/ -.vscode \ No newline at end of file +.vscode +remix-compiler.config.js From 72cb1117ecf2791fd67be03a444f7a445db9b77e Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 5 Sep 2022 10:02:08 +0700 Subject: [PATCH 049/190] Refactor code. Fix missing imports. --- test/sorting/MockSwapStorage.test.ts | 3 ++- test/sorting/QuickSort.test.ts | 29 +++++++++++++++++++++++++ test/validator/ValidatorSetCore.test.ts | 3 ++- 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 test/sorting/QuickSort.test.ts diff --git a/test/sorting/MockSwapStorage.test.ts b/test/sorting/MockSwapStorage.test.ts index aaa790ca0..d97d6659d 100644 --- a/test/sorting/MockSwapStorage.test.ts +++ b/test/sorting/MockSwapStorage.test.ts @@ -3,7 +3,7 @@ import { ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { MockSwapStorage, MockSwapStorage__factory } from '../../src/types'; -import { ValidatorCandidateStruct } from '../../src/types/ValidatorSetCoreMock'; +import { ValidatorCandidateStruct } from '../../src/types/IStaking'; let swapping: MockSwapStorage; @@ -24,6 +24,7 @@ const generateCandidate = ( stakedAmount: 0, delegatedAmount: 0, governing: false, + state: 0, ____gap: Array.apply(null, Array(20)).map((_) => 0), }; }; diff --git a/test/sorting/QuickSort.test.ts b/test/sorting/QuickSort.test.ts new file mode 100644 index 000000000..4e235214f --- /dev/null +++ b/test/sorting/QuickSort.test.ts @@ -0,0 +1,29 @@ +// import { expect } from 'chai'; +// import { deployments, ethers } from 'hardhat'; +// import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +// import { solidityKeccak256 } from 'ethers/lib/utils'; + +// import { +// QuickSort, +// QuickSort__factory, +// QuickSortMock, +// QuickSortMock__factory +// } from '../../src/types'; +// import { BigNumber } from 'ethers/lib/ethers'; + +// let quickSortLib: QuickSort; +// let quickSortContract: QuickSortMock; + +// let admin: SignerWithAddress; + +// describe('Quick sort', () => { +// before(async () => { +// [admin] = await ethers.getSigners(); +// quickSortLib = await new QuickSort__factory().deploy(); +// quickSortContract = await new QuickSortMock__factory(admin).deploy(); +// }); + +// describe('Operation functions', async () => { + +// }); +// }); diff --git a/test/validator/ValidatorSetCore.test.ts b/test/validator/ValidatorSetCore.test.ts index 19ded6b0e..1c157afb1 100644 --- a/test/validator/ValidatorSetCore.test.ts +++ b/test/validator/ValidatorSetCore.test.ts @@ -3,7 +3,7 @@ import { ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { MockValidatorSetCore, MockValidatorSetCore__factory } from '../../src/types'; -import { ValidatorCandidateStruct } from '../../src/types/ValidatorSetCoreMock'; +import { ValidatorCandidateStruct } from '../../src/types/IStaking'; let validatorsCore: MockValidatorSetCore; @@ -27,6 +27,7 @@ const generateCandidate = ( stakedAmount: 0, delegatedAmount: 0, governing: false, + state: 0, ____gap: Array.apply(null, Array(20)).map((_) => 0), }; }; From b44041b56ec770c6dd99a28c569320c0d5bf9d21 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 5 Sep 2022 13:21:08 +0700 Subject: [PATCH 050/190] [Staking] Optimize sorting --- contracts/staking/Staking.sol | 44 +++--- test/staking/Staking.test.ts | 281 +++++++++++++++++++++++++++------- 2 files changed, 251 insertions(+), 74 deletions(-) diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 3a54a4b5c..8c470fb2c 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -400,11 +400,8 @@ contract Staking is IStaking, Initializable { * * @return newValidatorSet Validator set for the new epoch */ - function updateValidatorSet() - external - onlyValidatorSetContract - returns (ValidatorCandidate[] memory newValidatorSet) - { + function updateValidatorSet() external onlyValidatorSetContract returns (address[] memory newValidatorSet) { + console.log("[ ] \t 1. gas left", gasleft()); /// checking global state, skipping sorting if unchanged console.log("[*] updateValidatorSet"); if (!_globalBalanceChanged) { @@ -423,56 +420,59 @@ contract Staking is IStaking, Initializable { } /// prepare sorting data - /// TODO(bao): only take active validators to sort uint _length = validatorCandidates.length; + uint _numOfActiveNodes = 0; Sorting.Node[] memory _nodes = new Sorting.Node[](_length); Sorting.Node[] memory _sortedNodes = new Sorting.Node[](_length); console.log("[ ] \t prepare data"); _nodes[0] = Sorting.Node(0, type(uint256).max); for (uint i = 1; i < _length; i++) { - ValidatorCandidate memory _validator = validatorCandidates[i]; - _nodes[i].key = i; - _nodes[i].value = (_validator.state == ValidatorState.ACTIVE) - ? _validator.stakedAmount + _validator.delegatedAmount - : 0; - - console.log("[ ] \t\t key, value", _nodes[i].key, _nodes[i].value); + if (validatorCandidates[i].state == ValidatorState.ACTIVE) { + _nodes[i].value = validatorCandidates[i].stakedAmount + validatorCandidates[i].delegatedAmount; + _numOfActiveNodes++; + } + console.log("[ ] \t\t key, value \t\t", _nodes[i].key, "\t", _nodes[i].value); } delete _currentValidatorIndexes; + + console.log("[ ] \t 3. gas left", gasleft()); + /// do sort _sortedNodes = Sorting.sortNodes(_nodes); + console.log("[ ] \t 4. gas left", gasleft()); + /// TODO(bao): pick M validators which are governance - uint _currentSetSize = (_length < numOfCabinets) ? _length : numOfCabinets; + uint _currentSetSize = (_numOfActiveNodes < numOfCabinets) ? (_numOfActiveNodes + 1) : (numOfCabinets + 1); console.log("[ ] \t after sort"); console.log("[ ] \t\t _currentSetSize", _currentSetSize); for (uint i = 0; i < _currentSetSize; i++) { - console.log("[ ] \t\t key, value", _sortedNodes[i].key, _sortedNodes[i].value); + console.log("[ ] \t\t key, value \t\t", _sortedNodes[i].key, "\t", _sortedNodes[i].value); _currentValidatorIndexes.push(_sortedNodes[i].key); } + console.log("[ ] \t 5. gas left", gasleft()); + return getCurrentValidatorSet(); } - - /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR QUERYING // /////////////////////////////////////////////////////////////////////////////////////// - function getCurrentValidatorSet() public view returns (ValidatorCandidate[] memory currentValidatorSet_) { + function getCurrentValidatorSet() public view returns (address[] memory currentValidatorSet_) { console.log("[*] getPendingRenouncingValidatorIndexes"); uint _length = _currentValidatorIndexes.length; - currentValidatorSet_ = new ValidatorCandidate[](_length); + currentValidatorSet_ = new address[](_length); for (uint i = 0; i < _length; i++) { - console.log("[ ] \t _i, _currentIndexes[i]", i, _currentValidatorIndexes[i]); - currentValidatorSet_[i] = validatorCandidates[_currentValidatorIndexes[i]]; + console.log("[ ] \t _i, _currentIndexes[i]", i, _currentValidatorIndexes[i]); + currentValidatorSet_[i] = validatorCandidates[_currentValidatorIndexes[i]].consensusAddr; } + return currentValidatorSet_; } - function getPendingRenouncingValidatorIndexes() public view returns (uint[] memory pendingRenouncingIndexes_) { uint _length = _pendingRenouncingValidatorIndexes.length; pendingRenouncingIndexes_ = new uint[](_length); diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index 997f6c5b7..393a30ca2 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -62,45 +62,45 @@ describe('Staking test', () => { let minValidatorBalance: BigNumber; describe('Single flow', async () => { - before(async () => { - [admin, ...signers] = await ethers.getSigners(); - await deployments.fixture('StakingContract'); - const stakingProxyDeployment = await deployments.get('StakingProxy'); - stakingContract = Staking__factory.connect(stakingProxyDeployment.address, admin); - - candidates = []; - stakingAddrs = []; - consensusAddrs = []; - treasuryAddrs = []; - - unstakingOnHoldBlocksNum = await stakingContract.unstakingOnHoldBlocksNum(); - minValidatorBalance = await stakingContract.minValidatorBalance(); - - console.log('Set validator set contract...'); - await stakingContract.setValidatorSetContract(admin.address); - - console.log('Init addresses for 10 candidates...'); - for (let i = 0; i < 10; i++) { - candidates.push( - generateCandidate(signers[3 * i].address, signers[3 * i + 1].address, signers[3 * i + 2].address) - ); - stakingAddrs.push(signers[3 * i]); - consensusAddrs.push(signers[3 * i + 1]); - treasuryAddrs.push(signers[3 * i + 2]); + describe('Validator functions', async () => { + before(async () => { + [admin, ...signers] = await ethers.getSigners(); + await deployments.fixture('StakingContract'); + const stakingProxyDeployment = await deployments.get('StakingProxy'); + stakingContract = Staking__factory.connect(stakingProxyDeployment.address, admin); + + candidates = []; + stakingAddrs = []; + consensusAddrs = []; + treasuryAddrs = []; + + unstakingOnHoldBlocksNum = await stakingContract.unstakingOnHoldBlocksNum(); + minValidatorBalance = await stakingContract.minValidatorBalance(); + + console.log('Set validator set contract...'); + await stakingContract.setValidatorSetContract(admin.address); + + console.log('Init addresses for 10 candidates...'); + for (let i = 0; i < 10; i++) { + candidates.push( + generateCandidate(signers[3 * i].address, signers[3 * i + 1].address, signers[3 * i + 2].address) + ); + stakingAddrs.push(signers[3 * i]); + consensusAddrs.push(signers[3 * i + 1]); + treasuryAddrs.push(signers[3 * i + 2]); - console.log( - i, - signers[3 * i].address, - signers[3 * i + 1].address, - signers[3 * i + 2].address, - (await ethers.provider.getBalance(signers[3 * i].address)).toString(), - (await ethers.provider.getBalance(signers[3 * i + 1].address)).toString(), - (await ethers.provider.getBalance(signers[3 * i + 2].address)).toString() - ); - } - }); + console.log( + i, + signers[3 * i].address, + signers[3 * i + 1].address, + signers[3 * i + 2].address, + (await ethers.provider.getBalance(signers[3 * i].address)).toString(), + (await ethers.provider.getBalance(signers[3 * i + 1].address)).toString(), + (await ethers.provider.getBalance(signers[3 * i + 2].address)).toString() + ); + } + }); - describe('Validator functions', async () => { describe('Proposing', async () => { it('Should be able to propose 1 validator', async () => { let tx = await stakingContract @@ -309,14 +309,9 @@ describe('Staking test', () => { } await stakingContract.connect(admin).updateValidatorSet(); - let currentSet = (await stakingContract.getCurrentValidatorSet()).map((x) => x.consensusAddr); + let currentSet = await stakingContract.getCurrentValidatorSet(); let expectingSet = [DEFAULT_ADDRESS].concat([5, 4, 3, 2, 1, 0].map((i) => consensusAddrs[i].address)); - console.log( - '>>> currentSet', - (await stakingContract.getCurrentValidatorSet()).map((x) => { - return { consensusAddrs: x.consensusAddr, amount: x.stakedAmount }; - }) - ); + console.log('>>> currentSet', currentSet); await expect(expectingSet).eql(currentSet); }); @@ -340,14 +335,9 @@ describe('Staking test', () => { it('Should be update validators list success', async () => { await stakingContract.connect(admin).updateValidatorSet(); - let currentSet = (await stakingContract.getCurrentValidatorSet()).map((x) => x.consensusAddr); + let currentSet = await stakingContract.getCurrentValidatorSet(); let expectingSet = [DEFAULT_ADDRESS].concat([5, 2, 1, 0].map((i) => consensusAddrs[i].address)); - console.log( - '>>> currentSet', - (await stakingContract.getCurrentValidatorSet()).map((x) => { - return { consensusAddrs: x.consensusAddr, amount: x.stakedAmount }; - }) - ); + console.log('>>> currentSet', currentSet); await expect(expectingSet).eql(currentSet); }); }); @@ -356,6 +346,193 @@ describe('Staking test', () => { describe('Delegator functions', async () => {}); - describe('Updating validator functions', async () => {}); + describe('Updating validator functions', async () => { + beforeEach(async () => { + [admin, ...signers] = await ethers.getSigners(); + await deployments.fixture('StakingContract'); + const stakingProxyDeployment = await deployments.get('StakingProxy'); + stakingContract = Staking__factory.connect(stakingProxyDeployment.address, admin); + + candidates = []; + stakingAddrs = []; + consensusAddrs = []; + treasuryAddrs = []; + + unstakingOnHoldBlocksNum = await stakingContract.unstakingOnHoldBlocksNum(); + minValidatorBalance = await stakingContract.minValidatorBalance(); + + console.log('Set validator set contract...'); + await stakingContract.setValidatorSetContract(admin.address); + + console.log('Init addresses for 21 candidates...'); + for (let i = 0; i < 21; i++) { + candidates.push( + generateCandidate(signers[3 * i].address, signers[3 * i + 1].address, signers[3 * i + 2].address) + ); + stakingAddrs.push(signers[3 * i]); + consensusAddrs.push(signers[3 * i + 1]); + treasuryAddrs.push(signers[3 * i + 2]); + + console.log( + i, + signers[3 * i].address, + signers[3 * i + 1].address, + signers[3 * i + 2].address, + (await ethers.provider.getBalance(signers[3 * i].address)).toString(), + (await ethers.provider.getBalance(signers[3 * i + 1].address)).toString(), + (await ethers.provider.getBalance(signers[3 * i + 2].address)).toString() + ); + } + }); + + it('Should 21 validator in increasing order sorted', async () => { + // balance: [3M, 3M+1, 3M+2, 3M+3, 3M+4, 3M+5, ...] + for (let i = 0; i < 21; ++i) { + let topupValue = minValidatorBalance.add(ethers.utils.parseEther(i.toString())); + let tx = await stakingContract + .connect(stakingAddrs[i]) + .proposeValidator(consensusAddrs[i].address, treasuryAddrs[i].address, i * 100, { + value: topupValue, + }); + expect(await tx) + .to.emit(stakingContract, 'ValidatorProposed') + .withArgs(consensusAddrs[i].address, stakingAddrs[i].address, topupValue, candidates[i]); + } + + await stakingContract.connect(admin).updateValidatorSet(); + let currentSet = await stakingContract.getCurrentValidatorSet(); + let expectingSet = [DEFAULT_ADDRESS].concat( + Array.from({ length: 21 }, (_, j) => 20 - j).map((i) => consensusAddrs[i].address) + ); + console.log('>>> currentSet', currentSet); + await expect(expectingSet).eql(currentSet); + }); + + it('Should 21 validator in mixed order sorted', async () => { + // balance: [3M, 3M+1, 3M+2, 3M+3, 3M+4, 3M+5, ...] + let balances = []; + for (let i = 0; i < 21; ++i) { + balances.push({ + key: i, + value: Math.floor(Math.random() * 1000), + }); + } + + for (let j = 0; j < 21; ++j) { + let i = balances[j].key; + let topupValue = minValidatorBalance.add(ethers.utils.parseEther(balances[j].value.toString())); + let tx = await stakingContract + .connect(stakingAddrs[i]) + .proposeValidator(consensusAddrs[i].address, treasuryAddrs[i].address, i * 100, { + value: topupValue, + }); + expect(await tx) + .to.emit(stakingContract, 'ValidatorProposed') + .withArgs(consensusAddrs[i].address, stakingAddrs[i].address, topupValue, candidates[i]); + } + + balances.sort((a, b) => (a.value < b.value ? 1 : a.value == b.value ? (a.key < b.key ? 1 : -1) : -1)); + console.log( + '>>> balances index after sort', + balances.map((e) => e.key + 1) + ); + + await stakingContract.connect(admin).updateValidatorSet(); + let currentSet = await stakingContract.getCurrentValidatorSet(); + let expectingSet = [DEFAULT_ADDRESS].concat(balances.map((e) => consensusAddrs[e.key].address)); + console.log('>>> currentSet', currentSet); + await expect(expectingSet).eql(currentSet); + }); + + it('Should second sort for 21 validator ignored', async () => { + // balance: [3M, 3M+1, 3M+2, 3M+3, 3M+4, 3M+5, ...] + let balances = []; + for (let i = 0; i < 21; ++i) { + balances.push({ + key: i, + value: Math.floor(Math.random() * 1000), + }); + } + + for (let j = 0; j < 21; ++j) { + let i = balances[j].key; + let topupValue = minValidatorBalance.add(ethers.utils.parseEther(balances[j].value.toString())); + let tx = await stakingContract + .connect(stakingAddrs[i]) + .proposeValidator(consensusAddrs[i].address, treasuryAddrs[i].address, i * 100, { + value: topupValue, + }); + expect(await tx) + .to.emit(stakingContract, 'ValidatorProposed') + .withArgs(consensusAddrs[i].address, stakingAddrs[i].address, topupValue, candidates[i]); + } + + balances.sort((a, b) => (a.value < b.value ? 1 : a.value == b.value ? (a.key < b.key ? 1 : -1) : -1)); + console.log( + '>>> balances index after sort', + balances.map((e) => e.key + 1) + ); + + // first sort + await stakingContract.connect(admin).updateValidatorSet(); + let currentSet = await stakingContract.getCurrentValidatorSet(); + let expectingSet = [DEFAULT_ADDRESS].concat(balances.map((e) => consensusAddrs[e.key].address)); + await expect(expectingSet).eql(currentSet); + + // second sort + await stakingContract.connect(admin).updateValidatorSet(); + currentSet = await stakingContract.getCurrentValidatorSet(); + expectingSet = [DEFAULT_ADDRESS].concat(balances.map((e) => consensusAddrs[e.key].address)); + await expect(expectingSet).eql(currentSet); + }); + + it('Should second and third sort for 21 validator ignored', async () => { + // balance: [3M, 3M+1, 3M+2, 3M+3, 3M+4, 3M+5, ...] + let balances = []; + for (let i = 0; i < 21; ++i) { + balances.push({ + key: i, + value: Math.floor(Math.random() * 1000), + }); + } + + for (let j = 0; j < 21; ++j) { + let i = balances[j].key; + let topupValue = minValidatorBalance.add(ethers.utils.parseEther(balances[j].value.toString())); + let tx = await stakingContract + .connect(stakingAddrs[i]) + .proposeValidator(consensusAddrs[i].address, treasuryAddrs[i].address, i * 100, { + value: topupValue, + }); + expect(await tx) + .to.emit(stakingContract, 'ValidatorProposed') + .withArgs(consensusAddrs[i].address, stakingAddrs[i].address, topupValue, candidates[i]); + } + + balances.sort((a, b) => (a.value < b.value ? 1 : a.value == b.value ? (a.key < b.key ? 1 : -1) : -1)); + console.log( + '>>> balances index after sort', + balances.map((e) => e.key + 1) + ); + + // first sort + await stakingContract.connect(admin).updateValidatorSet(); + let currentSet = await stakingContract.getCurrentValidatorSet(); + let expectingSet = [DEFAULT_ADDRESS].concat(balances.map((e) => consensusAddrs[e.key].address)); + await expect(expectingSet).eql(currentSet); + + // second sort + await stakingContract.connect(admin).updateValidatorSet(); + currentSet = await stakingContract.getCurrentValidatorSet(); + expectingSet = [DEFAULT_ADDRESS].concat(balances.map((e) => consensusAddrs[e.key].address)); + await expect(expectingSet).eql(currentSet); + + // third sort + await stakingContract.connect(admin).updateValidatorSet(); + currentSet = await stakingContract.getCurrentValidatorSet(); + expectingSet = [DEFAULT_ADDRESS].concat(balances.map((e) => consensusAddrs[e.key].address)); + await expect(expectingSet).eql(currentSet); + }); + }); }); }); From 833e41d70c5e9071604f29ca87ecae01c4b15b95 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 5 Sep 2022 13:36:04 +0700 Subject: [PATCH 051/190] [Staking] Fix sorting --- contracts/staking/Staking.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 8c470fb2c..31b441885 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -428,16 +428,15 @@ contract Staking is IStaking, Initializable { console.log("[ ] \t prepare data"); _nodes[0] = Sorting.Node(0, type(uint256).max); for (uint i = 1; i < _length; i++) { + ValidatorCandidate storage _candidate = validatorCandidates[i]; _nodes[i].key = i; - if (validatorCandidates[i].state == ValidatorState.ACTIVE) { - _nodes[i].value = validatorCandidates[i].stakedAmount + validatorCandidates[i].delegatedAmount; + if (_candidate.state == ValidatorState.ACTIVE) { + _nodes[i].value = _candidate.stakedAmount + _candidate.delegatedAmount; _numOfActiveNodes++; } console.log("[ ] \t\t key, value \t\t", _nodes[i].key, "\t", _nodes[i].value); } - delete _currentValidatorIndexes; - console.log("[ ] \t 3. gas left", gasleft()); /// do sort _sortedNodes = Sorting.sortNodes(_nodes); @@ -448,6 +447,7 @@ contract Staking is IStaking, Initializable { uint _currentSetSize = (_numOfActiveNodes < numOfCabinets) ? (_numOfActiveNodes + 1) : (numOfCabinets + 1); console.log("[ ] \t after sort"); console.log("[ ] \t\t _currentSetSize", _currentSetSize); + delete _currentValidatorIndexes; for (uint i = 0; i < _currentSetSize; i++) { console.log("[ ] \t\t key, value \t\t", _sortedNodes[i].key, "\t", _sortedNodes[i].value); _currentValidatorIndexes.push(_sortedNodes[i].key); From 5429333c9b7732251e954851da97977e3313c7f1 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 5 Sep 2022 13:50:04 +0700 Subject: [PATCH 052/190] [Staking] Update test --- test/staking/Staking.test.ts | 112 ++++++++++++++++------------------- 1 file changed, 51 insertions(+), 61 deletions(-) diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index 393a30ca2..c2354463d 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -11,10 +11,10 @@ let stakingContract: Staking; let signers: SignerWithAddress[]; let admin: SignerWithAddress; -let candidates: ValidatorCandidateStruct[]; -let stakingAddrs: SignerWithAddress[]; -let consensusAddrs: SignerWithAddress[]; -let treasuryAddrs: SignerWithAddress[]; +let candidates: ValidatorCandidateStruct[] = []; +let stakingAddrs: SignerWithAddress[] = []; +let consensusAddrs: SignerWithAddress[] = []; +let treasuryAddrs: SignerWithAddress[] = []; enum ValidatorStateEnum { ACTIVE = 0, @@ -61,44 +61,42 @@ describe('Staking test', () => { let unstakingOnHoldBlocksNum: BigNumber; let minValidatorBalance: BigNumber; + before(async () => { + [admin, ...signers] = await ethers.getSigners(); + + console.log('Init addresses for 21 candidates...'); + for (let i = 0; i < 21; i++) { + candidates.push( + generateCandidate(signers[3 * i].address, signers[3 * i + 1].address, signers[3 * i + 2].address) + ); + stakingAddrs.push(signers[3 * i]); + consensusAddrs.push(signers[3 * i + 1]); + treasuryAddrs.push(signers[3 * i + 2]); + + console.log( + i, + signers[3 * i].address, + signers[3 * i + 1].address, + signers[3 * i + 2].address, + (await ethers.provider.getBalance(signers[3 * i].address)).toString(), + (await ethers.provider.getBalance(signers[3 * i + 1].address)).toString(), + (await ethers.provider.getBalance(signers[3 * i + 2].address)).toString() + ); + } + }); + describe('Single flow', async () => { describe('Validator functions', async () => { before(async () => { - [admin, ...signers] = await ethers.getSigners(); await deployments.fixture('StakingContract'); const stakingProxyDeployment = await deployments.get('StakingProxy'); stakingContract = Staking__factory.connect(stakingProxyDeployment.address, admin); - candidates = []; - stakingAddrs = []; - consensusAddrs = []; - treasuryAddrs = []; - unstakingOnHoldBlocksNum = await stakingContract.unstakingOnHoldBlocksNum(); minValidatorBalance = await stakingContract.minValidatorBalance(); console.log('Set validator set contract...'); await stakingContract.setValidatorSetContract(admin.address); - - console.log('Init addresses for 10 candidates...'); - for (let i = 0; i < 10; i++) { - candidates.push( - generateCandidate(signers[3 * i].address, signers[3 * i + 1].address, signers[3 * i + 2].address) - ); - stakingAddrs.push(signers[3 * i]); - consensusAddrs.push(signers[3 * i + 1]); - treasuryAddrs.push(signers[3 * i + 2]); - - console.log( - i, - signers[3 * i].address, - signers[3 * i + 1].address, - signers[3 * i + 2].address, - (await ethers.provider.getBalance(signers[3 * i].address)).toString(), - (await ethers.provider.getBalance(signers[3 * i + 1].address)).toString(), - (await ethers.provider.getBalance(signers[3 * i + 2].address)).toString() - ); - } }); describe('Proposing', async () => { @@ -348,44 +346,39 @@ describe('Staking test', () => { describe('Updating validator functions', async () => { beforeEach(async () => { - [admin, ...signers] = await ethers.getSigners(); await deployments.fixture('StakingContract'); const stakingProxyDeployment = await deployments.get('StakingProxy'); stakingContract = Staking__factory.connect(stakingProxyDeployment.address, admin); - candidates = []; - stakingAddrs = []; - consensusAddrs = []; - treasuryAddrs = []; - unstakingOnHoldBlocksNum = await stakingContract.unstakingOnHoldBlocksNum(); minValidatorBalance = await stakingContract.minValidatorBalance(); console.log('Set validator set contract...'); await stakingContract.setValidatorSetContract(admin.address); + }); - console.log('Init addresses for 21 candidates...'); - for (let i = 0; i < 21; i++) { - candidates.push( - generateCandidate(signers[3 * i].address, signers[3 * i + 1].address, signers[3 * i + 2].address) - ); - stakingAddrs.push(signers[3 * i]); - consensusAddrs.push(signers[3 * i + 1]); - treasuryAddrs.push(signers[3 * i + 2]); - - console.log( - i, - signers[3 * i].address, - signers[3 * i + 1].address, - signers[3 * i + 2].address, - (await ethers.provider.getBalance(signers[3 * i].address)).toString(), - (await ethers.provider.getBalance(signers[3 * i + 1].address)).toString(), - (await ethers.provider.getBalance(signers[3 * i + 2].address)).toString() - ); + it('Should sort 21 validator in descreasing order', async () => { + // balance: [3M+20, 3M+19, 3M+18, 3M+17, 3M+16, 3M+15, ...] + for (let i = 0; i < 21; ++i) { + let topupValue = minValidatorBalance.add(ethers.utils.parseEther((20 - i).toString())); + let tx = await stakingContract + .connect(stakingAddrs[i]) + .proposeValidator(consensusAddrs[i].address, treasuryAddrs[i].address, i * 100, { + value: topupValue, + }); + expect(await tx) + .to.emit(stakingContract, 'ValidatorProposed') + .withArgs(consensusAddrs[i].address, stakingAddrs[i].address, topupValue, candidates[i]); } + + await stakingContract.connect(admin).updateValidatorSet(); + let currentSet = await stakingContract.getCurrentValidatorSet(); + let expectingSet = [DEFAULT_ADDRESS].concat([...Array(21).keys()].map((i) => consensusAddrs[i].address)); + console.log('>>> currentSet', currentSet); + await expect(expectingSet).eql(currentSet); }); - it('Should 21 validator in increasing order sorted', async () => { + it('Should sort 21 validator in increasing order', async () => { // balance: [3M, 3M+1, 3M+2, 3M+3, 3M+4, 3M+5, ...] for (let i = 0; i < 21; ++i) { let topupValue = minValidatorBalance.add(ethers.utils.parseEther(i.toString())); @@ -408,8 +401,7 @@ describe('Staking test', () => { await expect(expectingSet).eql(currentSet); }); - it('Should 21 validator in mixed order sorted', async () => { - // balance: [3M, 3M+1, 3M+2, 3M+3, 3M+4, 3M+5, ...] + it('Should sort 21 validator in mixed order', async () => { let balances = []; for (let i = 0; i < 21; ++i) { balances.push({ @@ -444,8 +436,7 @@ describe('Staking test', () => { await expect(expectingSet).eql(currentSet); }); - it('Should second sort for 21 validator ignored', async () => { - // balance: [3M, 3M+1, 3M+2, 3M+3, 3M+4, 3M+5, ...] + it('Should ignore the second sort for 21 validator', async () => { let balances = []; for (let i = 0; i < 21; ++i) { balances.push({ @@ -486,8 +477,7 @@ describe('Staking test', () => { await expect(expectingSet).eql(currentSet); }); - it('Should second and third sort for 21 validator ignored', async () => { - // balance: [3M, 3M+1, 3M+2, 3M+3, 3M+4, 3M+5, ...] + it('Should ignore the second and third sort for 21 validator', async () => { let balances = []; for (let i = 0; i < 21; ++i) { balances.push({ From e2a9c8f336313a5fad598c8e8233202697d77e5c Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Mon, 5 Sep 2022 16:56:36 +0700 Subject: [PATCH 053/190] [SlashIndicator] Update slash contract --- contracts/SlashIndicator.sol | 26 +++++++++++------------- contracts/interfaces/ISlashIndicator.sol | 9 ++++++++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index 3af15917c..78d818cec 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -7,13 +7,6 @@ import "./interfaces/IStaking.sol"; import "./interfaces/IValidatorSet.sol"; contract SlashIndicator is ISlashIndicator { - enum SlashType { - UNKNOWN, - MISDEMAENOR, - FELONY, - DOUBLE_SIGNING - } - /// Init configuration uint256 public constant MISDEMEANOR_THRESHOLD = 50; uint256 public constant FELONY_THRESHOLD = 150; @@ -34,7 +27,7 @@ contract SlashIndicator is ISlashIndicator { event SlashedValidator(address indexed validator, SlashType slashType); event ResetIndicators(); - + modifier onlyCoinbase() { require(msg.sender == block.coinbase, "Slash: Only coinbase"); _; @@ -56,7 +49,7 @@ contract SlashIndicator is ISlashIndicator { _; } - constructor () { + constructor() { misdemeanorThreshold = MISDEMEANOR_THRESHOLD; felonyThreshold = FELONY_THRESHOLD; } @@ -80,11 +73,14 @@ contract SlashIndicator is ISlashIndicator { * */ function slash(address _validatorAddr) external onlyInitialized onlyCoinbase oncePerBlock { - // Check if the to be slashed validator is in the current epoch - require(validatorSetContract.isCurrentValidator(_validatorAddr), "Slash: Cannot slash validator not in current epoch"); + // Check if the to be slashed validator is in the current epoch + require( + validatorSetContract.isCurrentValidator(_validatorAddr), + "Slash: Cannot slash validator not in current epoch" + ); Indicator storage indicator = indicators[_validatorAddr]; - + // Add the validator to the list if they are not exist yet if (indicator.historicalCounter == 0) { indicator.historicalCounter = 1; @@ -94,7 +90,7 @@ contract SlashIndicator is ISlashIndicator { indicator.historicalCounter++; indicator.counter++; } - + indicator.height = block.number; // Slash the validator as either the fenoly or the misdemeanor @@ -145,7 +141,7 @@ contract SlashIndicator is ISlashIndicator { */ function getSlashIndicator(address validator) external view returns (Indicator memory) { Indicator memory _indicator = indicators[validator]; - return _indicator; + return _indicator; } /** @@ -158,4 +154,6 @@ contract SlashIndicator is ISlashIndicator { function getSlashThresholds() external view returns (uint256, uint256) { return (misdemeanorThreshold, felonyThreshold); } + + function resetCounter(address) external override {} } diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index 3a0de505f..156a3f77a 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -3,6 +3,13 @@ pragma solidity ^0.8.9; interface ISlashIndicator { + enum SlashType { + UNKNOWN, + MISDEMAENOR, + FELONY, + DOUBLE_SIGNING + } + struct Indicator { /// @dev The block height that the indicator get updated, make sure this update once each block uint256 height; @@ -32,6 +39,8 @@ interface ISlashIndicator { */ function resetCounters() external; + function resetCounter(address) external; + /** * @notice Slash for double signing * From 5463ac89cde15152ee6d81032463ad106c52f96a Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Mon, 5 Sep 2022 16:57:26 +0700 Subject: [PATCH 054/190] [Staking] Rename & expose fn --- contracts/interfaces/IStaking.sol | 31 ++++++++++-- contracts/mocks/staking/MockStaking.sol | 4 +- .../staking/MockValidatorSetForStaking.sol | 6 +-- contracts/staking/DPoStaking.sol | 47 ++++++++++++++++--- contracts/staking/RewardCalculation.sol | 12 ++--- contracts/staking/Staking.sol | 13 ++++- 6 files changed, 91 insertions(+), 22 deletions(-) diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 1ca20a1d8..66d39d4ba 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -101,6 +101,11 @@ interface IStaking { */ function getValidatorCandidates() external view returns (ValidatorCandidate[] memory candidates); + /** + * @dev Returns the validator candidate weights. + */ + function getCandidateWeights() external view returns (address[] memory _candidates, uint256[] memory _weights); + /** * @dev Records the amount of reward `_reward` for the pending pool `_poolAddr`. * @@ -109,7 +114,7 @@ interface IStaking { * * Emits the `PendingPoolUpdated` event. * - * @notice This method should not be called after the pool is slashed. + * @notice This method should not be called after the pending pool is dropped. * */ function recordReward(address _consensusAddr, uint256 _reward) external; @@ -126,7 +131,7 @@ interface IStaking { function settleRewardPool(address _consensusAddr) external; /** - * @dev Handles when the validator pool is slashed. + * @dev Handles when the pending reward pool of the validator is dropped. * * Requirements: * - The method caller is validator contract. @@ -134,7 +139,7 @@ interface IStaking { * Emits the `PendingPoolUpdated` event. * */ - function onValidatorSlashed(address _consensusAddr) external; + function onRewardDropped(address _consensusAddr) external; /** * @dev Deducts from staking amount of the validator `_consensusAddr` for `_amount`. @@ -147,6 +152,26 @@ interface IStaking { */ function deductStakingAmount(address _consensusAddr, uint256 _amount) external; + /** + * @dev Returns the commission rate of the validator candidate `_consensusAddr`. + * + * Values in [0; 100_00] stands for 0-100%. + * + * Requirements: + * - The validator candidate is already existed. + * + */ + function commissionRateOf(address _consensusAddr) external view returns (uint256 _rate); + + /** + * @dev Returns the treasury address of the validator candidate `_consensusAddr`. + * + * Requirements: + * - The validator candidate is already existed. + * + */ + function treasuryAddressOf(address _consensusAddr) external view returns (address); + /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR VALIDATOR CANDIDATE // /////////////////////////////////////////////////////////////////////////////////////// diff --git a/contracts/mocks/staking/MockStaking.sol b/contracts/mocks/staking/MockStaking.sol index 12e1bca6f..b5632b8a9 100644 --- a/contracts/mocks/staking/MockStaking.sol +++ b/contracts/mocks/staking/MockStaking.sol @@ -42,7 +42,7 @@ contract MockStaking is RewardCalculation { function slash() external { uint256 _period = getPeriod(); _periodSlashed[_period] = true; - _onSlashed(poolAddr); + _onRewardDropped(poolAddr); } function recordReward(uint256 _rewardAmount) external { @@ -73,7 +73,7 @@ contract MockStaking is RewardCalculation { return _totalBalance; } - function _slashed(address, uint256 _period) internal view override returns (bool) { + function _rewardDropped(address, uint256 _period) internal view override returns (bool) { return _periodSlashed[_period]; } diff --git a/contracts/mocks/staking/MockValidatorSetForStaking.sol b/contracts/mocks/staking/MockValidatorSetForStaking.sol index 4a084adc9..6446d043a 100644 --- a/contracts/mocks/staking/MockValidatorSetForStaking.sol +++ b/contracts/mocks/staking/MockValidatorSetForStaking.sol @@ -32,16 +32,16 @@ contract MockValidatorSetForStaking is IValidatorSet { } function slashMisdemeanor(address _validator) external override { - stakingContract.onValidatorSlashed(_validator); + stakingContract.onRewardDropped(_validator); } function slashFelony(address _validator) external override { - stakingContract.onValidatorSlashed(_validator); + stakingContract.onRewardDropped(_validator); stakingContract.deductStakingAmount(_validator, 1); } function slashDoubleSign(address _validator) external override { - stakingContract.onValidatorSlashed(_validator); + stakingContract.onRewardDropped(_validator); } function periodOf(uint256 _block) external view override returns (uint256) { diff --git a/contracts/staking/DPoStaking.sol b/contracts/staking/DPoStaking.sol index 23098f0e6..4eadd9655 100644 --- a/contracts/staking/DPoStaking.sol +++ b/contracts/staking/DPoStaking.sol @@ -28,8 +28,8 @@ contract DPoStaking is IStaking, StakingManager, Initializable { mapping(address => uint256) internal _candidateIndex; /// @dev The validator candidate array. ValidatorCandidate[] public validatorCandidates; - /// @dev Mapping from consensus address => period index => indicating the period is slashed or not. - mapping(address => mapping(uint256 => bool)) internal _periodSlashed; + /// @dev Mapping from consensus address => period index => indicating the pending reward in the period is dropped or not. + mapping(address => mapping(uint256 => bool)) internal _pRewardDropped; modifier onlyGovernanceAdminContract() { require(msg.sender == _governanceAdminContract, "DPoStaking: method caller is not governance admin contract"); @@ -45,6 +45,10 @@ contract DPoStaking is IStaking, StakingManager, Initializable { _disableInitializers(); } + receive() external payable onlyValidatorContract {} + + fallback() external payable onlyValidatorContract {} + /** * @dev Initializes the contract storage. */ @@ -112,6 +116,19 @@ contract DPoStaking is IStaking, StakingManager, Initializable { return validatorCandidates; } + /** + * @inheritdoc IStaking + */ + function getCandidateWeights() external view returns (address[] memory _candidates, uint256[] memory _weights) { + uint256 _length = validatorCandidates.length; + _candidates = new address[](_length); + _weights = new uint256[](_length); + for (uint256 _i; _i < _length; _i++) { + _candidates[_i] = validatorCandidates[_i].consensusAddr; + _weights[_i] = validatorCandidates[_i].delegatedAmount; + } + } + /** * @inheritdoc IStaking */ @@ -131,10 +148,10 @@ contract DPoStaking is IStaking, StakingManager, Initializable { /** * @inheritdoc IStaking */ - function onValidatorSlashed(address _consensusAddr) external { + function onRewardDropped(address _consensusAddr) external { uint256 _period = _periodOf(block.number); - _periodSlashed[_consensusAddr][_period] = true; - _onSlashed(_consensusAddr); + _pRewardDropped[_consensusAddr][_period] = true; + _onRewardDropped(_consensusAddr); } /** @@ -146,6 +163,22 @@ contract DPoStaking is IStaking, StakingManager, Initializable { _undelegate(_consensusAddr, _candidate.candidateAdmin, _amount); } + /** + * @inheritdoc IStaking + */ + function commissionRateOf(address _consensusAddr) external view returns (uint256 _rate) { + ValidatorCandidate memory _candidate = _getCandidate(_consensusAddr); + return _candidate.commissionRate; + } + + /** + * @inheritdoc IStaking + */ + function treasuryAddressOf(address _consensusAddr) external view returns (address) { + ValidatorCandidate memory _candidate = _getCandidate(_consensusAddr); + return _candidate.treasuryAddr; + } + /////////////////////////////////////////////////////////////////////////////////////// // HELPER FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// @@ -236,8 +269,8 @@ contract DPoStaking is IStaking, StakingManager, Initializable { /** * @inheritdoc RewardCalculation */ - function _slashed(address _poolAddr, uint256 _period) internal view virtual override returns (bool) { - return _periodSlashed[_poolAddr][_period]; + function _rewardDropped(address _poolAddr, uint256 _period) internal view virtual override returns (bool) { + return _pRewardDropped[_poolAddr][_period]; } /** diff --git a/contracts/staking/RewardCalculation.sol b/contracts/staking/RewardCalculation.sol index 6972557bf..f4f91ba86 100644 --- a/contracts/staking/RewardCalculation.sol +++ b/contracts/staking/RewardCalculation.sol @@ -80,7 +80,7 @@ abstract contract RewardCalculation { PendingPool memory _pool = _pendingPool[_poolAddr]; uint256 _balance = balanceOf(_poolAddr, _user); - if (_slashed(_poolAddr, _periodOf(_reward.lastSyncBlock))) { + if (_rewardDropped(_poolAddr, _periodOf(_reward.lastSyncBlock))) { SettledRewardFields memory _sReward = _sUserReward[_poolAddr][_user]; uint256 _credited = (_sReward.accumulatedRps * _balance) / 1e18; return (_balance * _pool.accumulatedRps) / 1e18 + _sReward.debited - _credited; @@ -194,7 +194,7 @@ abstract contract RewardCalculation { * * Emits the `PendingPoolUpdated` event. * - * @notice This method should not be called after the pool is slashed. + * @notice This method should not be called after the pending pool is dropped. * */ function _recordReward(address _poolAddr, uint256 _reward) internal { @@ -206,14 +206,14 @@ abstract contract RewardCalculation { } /** - * @dev Handles when the pool `_poolAddr` is slashed. + * @dev Handles when the pool `_poolAddr` is dropped. * * Emits the `PendingPoolUpdated` event. * - * @notice This method should be called when the pool is slashed. + * @notice This method should be called when the pool is dropped. * */ - function _onSlashed(address _poolAddr) internal { + function _onRewardDropped(address _poolAddr) internal { uint256 _accumulatedRps = _settledPool[_poolAddr].accumulatedRps; PendingPool storage _pool = _pendingPool[_poolAddr]; _pool.accumulatedRps = _accumulatedRps; @@ -240,7 +240,7 @@ abstract contract RewardCalculation { /** * @dev Returns whether the pool is slashed in the period `_period`. */ - function _slashed(address _poolAddr, uint256 _period) internal view virtual returns (bool); + function _rewardDropped(address _poolAddr, uint256 _period) internal view virtual returns (bool); /** * @dev Returns the period from the block number. diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 31b441885..dd28b4a2e 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -495,7 +495,7 @@ contract Staking is IStaking, Initializable { function settleRewardPool(address _consensusAddr) external override {} - function onValidatorSlashed(address _consensusAddr) external override {} + function onRewardDropped(address _consensusAddr) external override {} function deductStakingAmount(address _consensusAddr, uint256 _amount) external override {} @@ -509,4 +509,15 @@ contract Staking is IStaking, Initializable { {} function claimRewards(address[] calldata _consensusAddrList) external override returns (uint256 _amount) {} + + function getCandidateWeights() + external + view + override + returns (address[] memory _candidates, uint256[] memory _weights) + {} + + function commissionRateOf(address _consensusAddr) external view override returns (uint256 _rate) {} + + function treasuryAddressOf(address _consensusAddr) external view override returns (address) {} } From dfea65d20aebce6a334e9bd49970996846c32b39 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Mon, 5 Sep 2022 16:57:45 +0700 Subject: [PATCH 055/190] [Lib] Add math lib --- contracts/libraries/Math.sol | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 contracts/libraries/Math.sol diff --git a/contracts/libraries/Math.sol b/contracts/libraries/Math.sol new file mode 100644 index 000000000..b763abd67 --- /dev/null +++ b/contracts/libraries/Math.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +library Math { + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a >= b ? a : b; + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } +} From e6d0e8c0e3fd5c5b5471d30106e23d3f9fa60824 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Mon, 5 Sep 2022 17:10:00 +0700 Subject: [PATCH 056/190] [RoninValidatorSet] add contract --- contracts/interfaces/IRoninValidatorSet.sol | 128 +++++++ contracts/libraries/Sorting.sol | 15 + .../ronin-validator/RoninValidatorSet.sol | 321 ++++++++++++++++++ 3 files changed, 464 insertions(+) create mode 100644 contracts/interfaces/IRoninValidatorSet.sol create mode 100644 contracts/ronin-validator/RoninValidatorSet.sol diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol new file mode 100644 index 000000000..9c237f89c --- /dev/null +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "./ISlashIndicator.sol"; + +interface IRoninValidatorSet { + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR COINBASE // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @dev Submits reward of the current block. + * + * Requirements: + * - The method caller is coinbase. + * + */ + function submitBlockReward() external payable; + + /** + * @dev Wraps up the current epoch. + * + * Requirements: + * - The method caller is coinbase. + * + */ + function wrapUpEpoch() external payable; + + /** + * @dev Returns the block that validator set was updated. + */ + function getLastUpdatedBlock() external view returns (uint256); + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR SLASH INDICATOR // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @dev Returns the governance admin contract address. + */ + function governanceAdminContract() external view returns (address); + + /** + * @dev Returns the slash indicator contract address. + */ + function slashIndicatorContract() external view returns (address); + + /** + * @dev Returns the staking contract address. + */ + function stakingContract() external view returns (address); + + /* @dev Slashes the validator that missed 50 block a day + * + * Requirements: + * - The method caller is slash indicator contract. + * + */ + function slashMisdemeanor(address _validatorAddr) external; + + /** + * @dev Slashes the validator that missed 150 block a day + * + * Requirements: + * - The method caller is slash indicator contract. + * + */ + function slashFelony(address _validatorAddr) external; + + /** + * @dev Slashes the validator that created 2 blocks on a same height + * + * Requirements: + * - The method caller is slash indicator contract. + * + */ + function slashDoubleSign(address _validatorAddr) external; + + /** + * @dev Returns whether the validators are put in jail (cannot join the set of validators) during the current period. + */ + function jailed(address[] memory) external view returns (bool[] memory); + + /** + * @dev Returns whether the incoming reward of the validators are dropped during the period. + */ + function noPendingReward(address[] memory, uint256 _period) external view returns (bool[] memory); + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR NORMAL USER // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @dev Returns the number of epochs in a period. + */ + function numberOfEpochsInPeriod() external view returns (uint256 _numberOfEpochs); + + /** + * @dev Returns the number of blocks in a epoch. + */ + function numberOfBlocksInEpoch() external view returns (uint256 _numberOfBlocks); + + /** + * @dev Returns the epoch index from the block number. + */ + function epochOf(uint256 _block) external view returns (uint256); + + /** + * @dev Returns the period index from the block number. + */ + function periodOf(uint256 _block) external view returns (uint256); + + /** + * @dev Returns the current validator list. + */ + function getValidators() external view returns (address[] memory); + + /** + * @dev Returns whether the epoch ended at the block number `_block`. + */ + function epochEnded(uint256 _block) external view returns (bool); + + /** + * @dev Returns whether the period ended at the block number `_block`. + */ + function periodEnded(uint256 _block) external view returns (bool); +} diff --git a/contracts/libraries/Sorting.sol b/contracts/libraries/Sorting.sol index db64b1e02..708ce03f0 100644 --- a/contracts/libraries/Sorting.sol +++ b/contracts/libraries/Sorting.sol @@ -17,6 +17,21 @@ library Sorting { return _quickSortNodes(nodes, int(0), int(nodes.length - 1)); } + function sort(address[] memory _keys, uint256[] memory _values) public pure returns (address[] memory) { + require(_keys.length > 0 && _values.length == _keys.length, "Sorting: invalid array length"); + Node[] memory _nodes = new Node[](_keys.length); + for (uint256 _i; _i < _nodes.length; _i++) { + _nodes[_i] = Node(uint256(uint160(_keys[_i])), _values[_i]); + } + _quickSortNodes(_nodes, int(0), int(_nodes.length - 1)); + + for (uint256 _i; _i < _nodes.length; _i++) { + _keys[_i] = address(uint160(_nodes[_i].key)); // Casting? + } + + return _keys; + } + function _quickSort( uint[] memory arr, int left, diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol new file mode 100644 index 000000000..ceb6e93e5 --- /dev/null +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import "../interfaces/ISlashIndicator.sol"; +import "../interfaces/IStaking.sol"; +import "../interfaces/IRoninValidatorSet.sol"; +import "../libraries/Sorting.sol"; +import "../libraries/Math.sol"; + +contract RoninValidatorSet is IRoninValidatorSet { + /// @dev Governance admin contract address. + address internal _governanceAdminContract; // TODO(Thor): add setter. + /// @dev Slash indicator contract address. + address internal _slashIndicatorContract; // Change type to address for testing purpose + /// @dev Staking contract address. + address internal _stakingContract; // Change type to address for testing purpose + /// @dev The maximum number of validator. + uint256 internal _maxValidatorNumber; + + /// @dev The total of validators + uint256 public validatorCount; + /// @dev Mapping from validator index => validator address + mapping(uint256 => address) internal _validator; + /// @dev Mapping from validator address => bool + mapping(address => bool) internal _validatorMap; + + /// @dev Returns the number of epochs in a period + uint256 internal _numberOfBlocksInEpoch; + /// @dev The number of blocks in a epoch + uint256 internal _numberOfEpochsInPeriod; + /// @dev The last updated block + uint256 internal _lastUpdatedBlock; + + /// @dev Mapping from period index => validator address => flag indicating whether the validator has no pending reward in that period + mapping(uint256 => mapping(address => bool)) internal _noPendingReward; + /// @dev Mapping from validator address => the last block that the validator is jailed + mapping(address => uint256) internal _jailedUntil; + /// @dev Mapping from valdiator address => incoming reward amount + mapping(address => uint256) internal _pendingReward; + + /// @dev The amount of RON to slash felony. + uint256 public slashFelonyAmount; + /// @dev The amount of RON to slash double sign. + uint256 public slashDoubleSignAmount; + + modifier onlyCoinbase() { + require(msg.sender == block.coinbase, "RoninValidatorSet: method caller is not coinbase"); + _; + } + + modifier whenEndEpoch() { + require(epochEnded(block.number), "RoninValidatorSet: only allowed at the end of epoch"); + _; + } + + constructor() {} + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR COINBASE // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @inheritdoc IRoninValidatorSet + */ + function submitBlockReward() external payable override onlyCoinbase { + uint256 _reward = msg.value; + address _validatorAddr = msg.sender; + if (!_isValidator(_validatorAddr)) { + // TODO(Thor): emit the deprecated reward event + return; + } + + if (_jailed(_validatorAddr)) { + // TODO(Thor): emit the deprecated reward event + return; + } + + IStaking _staking = IStaking(_stakingContract); + // Assumes the staking contract returns the rate value in range [0; 100_00] + uint256 _rate = _staking.commissionRateOf(_validatorAddr); + uint256 _mintingReward = (_rate * _reward) / 100_00; + + uint256 _amount = _reward - _mintingReward; + IStaking(_stakingContract).recordReward(_validatorAddr, _reward - _mintingReward); + // (1): We assume that we don't take back the reward recored for the delegator in the current epoch + // even when the validator is slashed. Therefore, we transfer we the RON amount to staking contract. + // TODO(Thor): use `call` to transfer reward with reentrancy gruard + require(payable(_stakingContract).send(_amount), "RoninValidatorSet: could not transfer RON"); + + if (!_noPendingReward[periodOf(block.number)][_validatorAddr]) { + _pendingReward[_validatorAddr] += _mintingReward; + // TODO(Thor): emit event + } + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function wrapUpEpoch() external payable override onlyCoinbase whenEndEpoch { + if (periodEnded(block.number)) { + IStaking _staking = IStaking(_stakingContract); + ISlashIndicator _slashIndicator = ISlashIndicator(_slashIndicatorContract); + + for (uint _i = 0; _i < validatorCount; _i++) { + address _validatorAddr = _validator[_i]; + // Same reason with (1), then we do not filter the slashed validator. + _staking.settleRewardPool(_validatorAddr); + _slashIndicator.resetCounter(_validatorAddr); + + uint256 _rewardAmount = _pendingReward[_validatorAddr]; + if (!_jailed(_validatorAddr) && !_noPendingReward[periodOf(block.number)][_validatorAddr]) { + // TODO(Thor): use `call` to transfer reward with reentrancy gruard + require( + payable(_staking.treasuryAddressOf(_validatorAddr)).send(_rewardAmount), + "RoninValidatorSet: could not transfer RON" + ); + } + _pendingReward[_validatorAddr] = 0; + } + } + _updateValidatorSet(); + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function getLastUpdatedBlock() external view returns (uint256) { + return _lastUpdatedBlock; + } + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR SLASH INDICATOR // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @inheritdoc IRoninValidatorSet + */ + function governanceAdminContract() external view override returns (address) { + return _governanceAdminContract; + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function slashIndicatorContract() external view override returns (address) { + return _slashIndicatorContract; + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function stakingContract() external view override returns (address) { + return _stakingContract; + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function slashMisdemeanor(address _validatorAddr) public override { + _noPendingReward[periodOf(block.number)][_validatorAddr] = true; + _pendingReward[_validatorAddr] = 0; + // We assume that current slashing won't effect to the recorded reward of the staking pool + // So we do not call the method `onRewardDropped` to the staking contract: + // ``` + // IStaking(_stakingContract).onRewardDropped(_validatorAddr); + //``` + // TODO(Thor): emit event + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function slashFelony(address _validatorAddr) external override { + slashMisdemeanor(_validatorAddr); + IStaking(_stakingContract).deductStakingAmount(_validatorAddr, slashFelonyAmount); + uint256 _jailedBlock = block.number + 2 * 28800; // TODO: make this constant number to variable + _jailedUntil[_validatorAddr] = Math.max(_jailedUntil[_validatorAddr], _jailedBlock); + // TODO(Thor): emit event + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function slashDoubleSign(address _validatorAddr) external override { + slashMisdemeanor(_validatorAddr); + IStaking(_stakingContract).deductStakingAmount(_validatorAddr, slashDoubleSignAmount); + _jailedUntil[_validatorAddr] = type(uint256).max; + // TODO(Thor): emit event + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function jailed(address[] memory _addrList) external view override returns (bool[] memory _result) { + for (uint256 _i; _i < _addrList.length; _i++) { + _result[_i] = _jailed(_addrList[_i]); + } + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function noPendingReward(address[] memory _addrList, uint256 _period) + external + view + override + returns (bool[] memory _result) + { + for (uint256 _i; _i < _addrList.length; _i++) { + _result[_i] = _noPendingReward[_period][_addrList[_i]]; + } + } + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR NORMAL USER // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @inheritdoc IRoninValidatorSet + */ + function epochOf(uint256 _block) public view override returns (uint256) { + return _block / _numberOfBlocksInEpoch + 1; + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function periodOf(uint256 _block) public view override returns (uint256) { + return _block / (_numberOfBlocksInEpoch * _numberOfEpochsInPeriod) + 1; + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function getValidators() external view override returns (address[] memory) {} + + /** + * @inheritdoc IRoninValidatorSet + */ + function epochEnded(uint256 _block) public view returns (bool) { + return _block % _numberOfBlocksInEpoch == _numberOfBlocksInEpoch - 1; + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function periodEnded(uint256 _block) public view returns (bool) { + uint256 _blockLength = _numberOfBlocksInEpoch * _numberOfEpochsInPeriod; + return _block % _blockLength == _blockLength - 1; + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function numberOfEpochsInPeriod() external view override returns (uint256 _numberOfEpochs) { + return _numberOfEpochsInPeriod; + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function numberOfBlocksInEpoch() external view override returns (uint256 _numberOfBlocks) { + return _numberOfBlocksInEpoch; + } + + /** + * @dev Returns whether the reward of the validator is put in jail (cannot join the set of validators) during the current period. + */ + function _jailed(address _validatorAddr) internal view returns (bool) { + return block.number <= _jailedUntil[_validatorAddr]; + } + + /** + * @dev Returns whether the address `_addr` is validator or not. + */ + function _isValidator(address _addr) internal view returns (bool) { + return _validatorMap[_addr]; + } + + /** + * @dev Updates the validator set based on the validator candidates from the Staking contract. + */ + function _updateValidatorSet() internal { + (address[] memory _candidates, uint256[] memory _weights) = IStaking(_stakingContract).getCandidateWeights(); + uint256 _newLength = _candidates.length; + for (uint256 _i; _i < _candidates.length; _i++) { + if (_jailed(_candidates[_i])) { + _newLength--; + _candidates[_i] = _candidates[_newLength]; + _weights[_i] = _weights[_newLength]; + } + } + + assembly { + mstore(_candidates, _newLength) + mstore(_weights, _newLength) + } + + _candidates = Sorting.sort(_candidates, _weights); + uint256 _newValidatorCount = Math.min(_maxValidatorNumber, _candidates.length); + + for (uint256 _i = _newValidatorCount; _i < validatorCount; _i++) { + delete _validator[_i]; + delete _validatorMap[_validator[_i]]; + } + + for (uint256 _i = 0; _i < _newValidatorCount; _i++) { + delete _validatorMap[_validator[_i]]; + + address _newValidator = _candidates[_i]; + _validatorMap[_newValidator] = true; + _validator[_i] = _newValidator; + } + + validatorCount = _newValidatorCount; + _lastUpdatedBlock = block.number; + // TODO(Thor): emit validator set updated. + } +} From 0aba9c1f75eb2400566c4ef81179e3294ab2267e Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Mon, 5 Sep 2022 17:13:00 +0700 Subject: [PATCH 057/190] [RoninValidatorSet] update get validator function --- contracts/ronin-validator/RoninValidatorSet.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index ceb6e93e5..b32af3ecd 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -234,7 +234,12 @@ contract RoninValidatorSet is IRoninValidatorSet { /** * @inheritdoc IRoninValidatorSet */ - function getValidators() external view override returns (address[] memory) {} + function getValidators() external view override returns (address[] memory _validatorList) { + _validatorList = new address[](validatorCount); + for (uint _i = 0; _i < _validatorList.length; _i++) { + _validatorList[_i] = _validator[_i]; + } + } /** * @inheritdoc IRoninValidatorSet From 06c599c34662daf3fffda81b23d2cf645d7c5074 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 5 Sep 2022 18:13:42 +0700 Subject: [PATCH 058/190] [Slash] Remove historical data. Implement resetCounter(s). --- contracts/interfaces/ISlashIndicator.sol | 16 ++++--- contracts/{ => slash}/SlashIndicator.sol | 55 +++++++++++------------- 2 files changed, 34 insertions(+), 37 deletions(-) rename contracts/{ => slash}/SlashIndicator.sol (72%) diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index 156a3f77a..3fdfa82eb 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -12,11 +12,9 @@ interface ISlashIndicator { struct Indicator { /// @dev The block height that the indicator get updated, make sure this update once each block - uint256 height; + uint256 lastSyncedBlock; /// @dev Number of missed block the validator, reset everyday or once reaching the fenoly threshold uint128 counter; - /// @dev Tracking the misbehavior records the validator - uint128 historicalCounter; } /** @@ -32,15 +30,21 @@ interface ISlashIndicator { function slash(address valAddr) external; /** - * @dev Reset the counter of the validator everyday + * @dev Reset the counter of the validator at the end of every period * * Requirements: * - Only validator contract can call this method */ - function resetCounters() external; - function resetCounter(address) external; + /** + * @dev Reset the counter of all validators at the end of every period + * + * Requirements: + * - Only validator contract can call this method + */ + function resetCounters(address[] calldata) external; + /** * @notice Slash for double signing * diff --git a/contracts/SlashIndicator.sol b/contracts/slash/SlashIndicator.sol similarity index 72% rename from contracts/SlashIndicator.sol rename to contracts/slash/SlashIndicator.sol index 78d818cec..96138796c 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/slash/SlashIndicator.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.9; -import "./interfaces/ISlashIndicator.sol"; -import "./interfaces/IStaking.sol"; -import "./interfaces/IValidatorSet.sol"; +import "../interfaces/ISlashIndicator.sol"; +import "../interfaces/IStaking.sol"; +import "../interfaces/IValidatorSet.sol"; contract SlashIndicator is ISlashIndicator { /// Init configuration @@ -26,6 +26,7 @@ contract SlashIndicator is ISlashIndicator { IStaking public stakingContract; event SlashedValidator(address indexed validator, SlashType slashType); + event ResetIndicator(address indexed validator); event ResetIndicators(); modifier onlyCoinbase() { @@ -72,7 +73,7 @@ contract SlashIndicator is ISlashIndicator { * - Only coinbase can call this method * */ - function slash(address _validatorAddr) external onlyInitialized onlyCoinbase oncePerBlock { + function slash(address _validatorAddr) external override onlyInitialized onlyCoinbase oncePerBlock { // Check if the to be slashed validator is in the current epoch require( validatorSetContract.isCurrentValidator(_validatorAddr), @@ -81,17 +82,8 @@ contract SlashIndicator is ISlashIndicator { Indicator storage indicator = indicators[_validatorAddr]; - // Add the validator to the list if they are not exist yet - if (indicator.historicalCounter == 0) { - indicator.historicalCounter = 1; - indicator.counter = 1; - validators.push(_validatorAddr); - } else { - indicator.historicalCounter++; - indicator.counter++; - } - - indicator.height = block.number; + indicator.counter++; + indicator.lastSyncedBlock = block.number; // Slash the validator as either the fenoly or the misdemeanor if (indicator.counter == felonyThreshold) { @@ -110,19 +102,23 @@ contract SlashIndicator is ISlashIndicator { * Requirements: * - Only validator contract can call this method */ - function resetCounters() external onlyInitialized onlyValidatorContract { - if (validators.length == 0) { + function resetCounters(address[] calldata _validatorAddrs) external override onlyInitialized onlyValidatorContract { + if (_validatorAddrs.length == 0) { return; } - for (uint i = 0; i < validators.length; i++) { - Indicator storage _indicator = indicators[validators[i]]; - _indicator.counter = 0; + for (uint i = 0; i < _validatorAddrs.length; i++) { + _resetCounter(_validatorAddrs[i]); } emit ResetIndicators(); } + function resetCounter(address _validatorAddr) external override onlyInitialized onlyValidatorContract { + _resetCounter(_validatorAddr); + emit ResetIndicator(_validatorAddr); + } + /** * @notice Slash for double signing * @@ -132,28 +128,25 @@ contract SlashIndicator is ISlashIndicator { * - Only coinbase can call this method * */ - function slashDoubleSign(address valAddr, bytes calldata evidence) external onlyInitialized onlyCoinbase { + function slashDoubleSign(address valAddr, bytes calldata evidence) external override onlyInitialized onlyCoinbase { revert("Not implemented"); } /** * @notice Get slash indicator of a validator */ - function getSlashIndicator(address validator) external view returns (Indicator memory) { + function getSlashIndicator(address validator) external view override returns (Indicator memory) { Indicator memory _indicator = indicators[validator]; return _indicator; } - /** - * @notice Get all validators which have slash information - */ - function getSlashValidators() external view returns (address[] memory) { - return validators; - } - - function getSlashThresholds() external view returns (uint256, uint256) { + function getSlashThresholds() external view override returns (uint256, uint256) { return (misdemeanorThreshold, felonyThreshold); } - function resetCounter(address) external override {} + function _resetCounter(address _validatorAddr) private { + Indicator storage _indicator = indicators[_validatorAddr]; + _indicator.counter = 0; + _indicator.lastSyncedBlock = block.number; + } } From 55a56d7a5dc579e2ebf5ff900bcfe972930d15f4 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Mon, 5 Sep 2022 18:55:57 +0700 Subject: [PATCH 059/190] [Staking] update test & fix contract bug --- contracts/interfaces/IStaking.sol | 2 +- .../staking/MockValidatorSetForStaking.sol | 17 +- contracts/staking/DPoStaking.sol | 20 +- contracts/staking/Staking.sol | 2 +- contracts/staking/StakingManager.sol | 6 +- test/staking/DPoStaking.test.ts | 553 +++++------------- 6 files changed, 168 insertions(+), 432 deletions(-) diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 66d39d4ba..c690c61c2 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -117,7 +117,7 @@ interface IStaking { * @notice This method should not be called after the pending pool is dropped. * */ - function recordReward(address _consensusAddr, uint256 _reward) external; + function recordReward(address _consensusAddr, uint256 _reward) external payable; /** * @dev Settles the pending pool and allocates rewards for the validator. diff --git a/contracts/mocks/staking/MockValidatorSetForStaking.sol b/contracts/mocks/staking/MockValidatorSetForStaking.sol index 6446d043a..c3e817cbc 100644 --- a/contracts/mocks/staking/MockValidatorSetForStaking.sol +++ b/contracts/mocks/staking/MockValidatorSetForStaking.sol @@ -10,6 +10,9 @@ contract MockValidatorSetForStaking is IValidatorSet { uint256 public numberOfEpochsInPeriod; uint256 public numberOfBlocksInEpoch; + /// @dev Mapping from period number => slashed + mapping(uint256 => bool) internal _periodSlashed; + uint256[] internal _periods; constructor( IStaking _stakingContract, @@ -22,7 +25,7 @@ contract MockValidatorSetForStaking is IValidatorSet { } function depositReward() external payable override { - stakingContract.recordReward(msg.sender, msg.value); + stakingContract.recordReward{ value: msg.value }(msg.sender, msg.value); } function settledReward(address[] memory _validatorList) external { @@ -44,8 +47,16 @@ contract MockValidatorSetForStaking is IValidatorSet { stakingContract.onRewardDropped(_validator); } - function periodOf(uint256 _block) external view override returns (uint256) { - return _block / numberOfBlocksInEpoch / numberOfEpochsInPeriod + 1; + function endPeriod() external { + _periods.push(block.number); + } + + function periodOf(uint256 _block) external view override returns (uint256 _period) { + for (uint256 _i; _i < _periods.length; _i++) { + if (_block >= _periods[_i]) { + _period = _i + 1; + } + } } function updateValidators() external override returns (address[] memory) {} diff --git a/contracts/staking/DPoStaking.sol b/contracts/staking/DPoStaking.sol index 4eadd9655..b6103b80b 100644 --- a/contracts/staking/DPoStaking.sol +++ b/contracts/staking/DPoStaking.sol @@ -124,16 +124,22 @@ contract DPoStaking is IStaking, StakingManager, Initializable { _candidates = new address[](_length); _weights = new uint256[](_length); for (uint256 _i; _i < _length; _i++) { - _candidates[_i] = validatorCandidates[_i].consensusAddr; - _weights[_i] = validatorCandidates[_i].delegatedAmount; + ValidatorCandidate storage _candidate = validatorCandidates[_i]; + _candidates[_i] = _candidate.consensusAddr; + _weights[_i] = _candidate.delegatedAmount; } } /** * @inheritdoc IStaking */ - function recordReward(address _consensusAddr, uint256 _reward) external onlyValidatorContract { - console.log("*** =>>>>>>> recordReward", _consensusAddr, _reward); + function recordReward(address _consensusAddr, uint256 _reward) external payable onlyValidatorContract { + console.log( + "*** =>>>>>>> recordReward", + _reward, + _pendingPool[_consensusAddr].accumulatedRps, + totalBalance(_consensusAddr) + ); _recordReward(_consensusAddr, _reward); console.log("*** =>>>>>>> recordReward", _pendingPool[_consensusAddr].accumulatedRps); } @@ -158,7 +164,7 @@ contract DPoStaking is IStaking, StakingManager, Initializable { * @inheritdoc IStaking */ function deductStakingAmount(address _consensusAddr, uint256 _amount) external onlyValidatorContract { - ValidatorCandidate memory _candidate = _getCandidate(_consensusAddr); + ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); _unstake(_consensusAddr, _candidate.candidateAdmin, _amount); _undelegate(_consensusAddr, _candidate.candidateAdmin, _amount); } @@ -167,7 +173,7 @@ contract DPoStaking is IStaking, StakingManager, Initializable { * @inheritdoc IStaking */ function commissionRateOf(address _consensusAddr) external view returns (uint256 _rate) { - ValidatorCandidate memory _candidate = _getCandidate(_consensusAddr); + ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); return _candidate.commissionRate; } @@ -175,7 +181,7 @@ contract DPoStaking is IStaking, StakingManager, Initializable { * @inheritdoc IStaking */ function treasuryAddressOf(address _consensusAddr) external view returns (address) { - ValidatorCandidate memory _candidate = _getCandidate(_consensusAddr); + ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); return _candidate.treasuryAddr; } diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index dd28b4a2e..62c4f2b2b 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -491,7 +491,7 @@ contract Staking is IStaking, Initializable { function getValidatorCandidates() external view override returns (ValidatorCandidate[] memory candidates) {} - function recordReward(address _consensusAddr, uint256 _reward) external override {} + function recordReward(address _consensusAddr, uint256 _reward) external payable override {} function settleRewardPool(address _consensusAddr) external override {} diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 3ccbbced0..c55be0454 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -32,8 +32,8 @@ abstract contract StakingManager is IStaking, RewardCalculation { * @inheritdoc RewardCalculation */ function totalBalance(address _poolAddr) public view override returns (uint256) { - ValidatorCandidate memory _candidate = _getCandidate(_poolAddr); - return _candidate.stakedAmount + _candidate.delegatedAmount; + ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); + return _candidate.delegatedAmount; } function minValidatorBalance() public view virtual returns (uint256); @@ -325,7 +325,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); _candidate.delegatedAmount -= _amount; - _delegatedAmount[_poolAddr][_user] = _amount; + _delegatedAmount[_poolAddr][_user] = _newBalance; emit Undelegated(_user, _poolAddr, _amount); } diff --git a/test/staking/DPoStaking.test.ts b/test/staking/DPoStaking.test.ts index a9c57d1a6..dd5c8b1f8 100644 --- a/test/staking/DPoStaking.test.ts +++ b/test/staking/DPoStaking.test.ts @@ -3,19 +3,14 @@ import { expect } from 'chai'; import { BigNumber, BigNumberish } from 'ethers'; import { ethers, network } from 'hardhat'; -import { - DPoStaking, - DPoStaking__factory, - MockStaking__factory, - TransparentUpgradeableProxy__factory, -} from '../../src/types'; +import { DPoStaking, DPoStaking__factory, TransparentUpgradeableProxy__factory } from '../../src/types'; import { MockValidatorSetForStaking__factory } from '../../src/types/factories/MockValidatorSetForStaking__factory'; import { MockValidatorSetForStaking } from '../../src/types/MockValidatorSetForStaking'; const EPS = 1; -let poolAddr: string = ethers.constants.AddressZero; -let otherPoolAddr: string = ethers.constants.AddressZero; +let poolAddr: SignerWithAddress; +let otherPoolAddr: SignerWithAddress; let deployer: SignerWithAddress; let proxyAdmin: SignerWithAddress; let userA: SignerWithAddress; @@ -30,9 +25,9 @@ const local = { claimableRewardForA: BigNumber.from(0), claimableRewardForB: BigNumber.from(0), recordReward: async function (reward: BigNumberish) { - const totalStaked = await stakingContract.totalBalance(poolAddr); - const stakingAmountA = await stakingContract.balanceOf(poolAddr, userA.address); - const stakingAmountB = await stakingContract.balanceOf(poolAddr, userB.address); + const totalStaked = await stakingContract.totalBalance(poolAddr.address); + const stakingAmountA = await stakingContract.balanceOf(poolAddr.address, userA.address); + const stakingAmountB = await stakingContract.balanceOf(poolAddr.address, userB.address); this.accumulatedRewardForA = this.accumulatedRewardForA.add( BigNumber.from(reward).mul(stakingAmountA).div(totalStaked) ); @@ -65,27 +60,25 @@ const local = { }; const expectLocalCalculationRight = async () => { - console.log('expectLocalCalculationRight', poolAddr); - { - const userReward = await stakingContract.getTotalReward(poolAddr, userA.address); + const userReward = await stakingContract.getTotalReward(poolAddr.address, userA.address); expect( userReward.sub(local.accumulatedRewardForA).abs().lte(EPS), `invalid user reward for A expected=${local.accumulatedRewardForA.toString()} actual=${userReward}` ).to.be.true; - const claimableReward = await stakingContract.getClaimableReward(poolAddr, userA.address); + const claimableReward = await stakingContract.getClaimableReward(poolAddr.address, userA.address); expect( claimableReward.sub(local.claimableRewardForA).abs().lte(EPS), `invalid claimable reward for A expected=${local.claimableRewardForA.toString()} actual=${claimableReward}` ).to.be.true; } { - const userReward = await stakingContract.getTotalReward(poolAddr, userB.address); + const userReward = await stakingContract.getTotalReward(poolAddr.address, userB.address); expect( userReward.sub(local.accumulatedRewardForB).abs().lte(EPS), `invalid user reward for B expected=${local.accumulatedRewardForB.toString()} actual=${userReward}` ).to.be.true; - const claimableReward = await stakingContract.getClaimableReward(poolAddr, userB.address); + const claimableReward = await stakingContract.getClaimableReward(poolAddr.address, userB.address); expect( claimableReward.sub(local.claimableRewardForB).abs().lte(EPS), `invalid claimable reward for B expected=${local.claimableRewardForB.toString()} actual=${claimableReward}` @@ -93,11 +86,12 @@ const expectLocalCalculationRight = async () => { } }; -const minValidatorBalance = BigNumber.from(10).pow(18); +const minValidatorBalance = BigNumber.from(0); describe('DPoStaking test', () => { before(async () => { [deployer, proxyAdmin, userA, userB, ...validatorCandidates] = await ethers.getSigners(); + validatorCandidates = validatorCandidates.slice(0, 2); const nonce = await deployer.getTransactionCount(); const proxyContractAddress = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); validatorContract = await new MockValidatorSetForStaking__factory(deployer).deploy(proxyContractAddress, 10, 2); @@ -115,410 +109,135 @@ describe('DPoStaking test', () => { ); stakingContract = DPoStaking__factory.connect(proxyContract.address, deployer); expect(proxyContractAddress.toLowerCase()).eq(proxyContract.address.toLowerCase()); - poolAddr = validatorCandidates[0].address; - otherPoolAddr = validatorCandidates[1].address; + poolAddr = validatorCandidates[0]; + otherPoolAddr = validatorCandidates[1]; }); - it('Should not be able to propose validator with insufficient amount', async () => { - await expect(stakingContract.proposeValidator(userA.address, userA.address, 1)).revertedWith( - 'StakingManager: insufficient amount' - ); - }); - - it('Should be able to propose validator with sufficient amount', async () => { - for (let i = 0; i < validatorCandidates.length; i++) { - const candidate = validatorCandidates[i]; - await stakingContract.connect(candidate).proposeValidator( - candidate.address, - candidate.address, - 1, // 0.01% - { value: minValidatorBalance } + describe('Validator candidate test', () => { + it('Should not be able to propose validator with insufficient amount', async () => { + await expect(stakingContract.proposeValidator(userA.address, userA.address, 1)).revertedWith( + 'StakingManager: insufficient amount' ); - } - await network.provider.send('evm_setAutomine', [false]); + }); + + it('Should be able to propose validator with sufficient amount', async () => { + for (let i = 0; i < validatorCandidates.length; i++) { + const candidate = validatorCandidates[i]; + await stakingContract.connect(candidate).proposeValidator( + candidate.address, + candidate.address, + 1, // 0.01% + { value: minValidatorBalance } + ); + } + await network.provider.send('evm_setAutomine', [false]); + }); + + it('Should not be able to call stake/unstake when the method is not the candidate owner', async () => {}); + + it('Should be able to stake/unstake as a validator', async () => {}); + + it('Should be not able to unstake with the balance left is not larger than the minimum balance threshold', async () => {}); }); - it('Should work properly with staking actions occurring sequentially for a normal period', async () => { - console.log({ poolAddr, otherPoolAddr }); - console.log([userA.address, userB.address]); - - // TODO: subtract commission rate in contract - console.log('0'); - - await stakingContract.connect(userA).delegate(poolAddr, { value: 100 }); - console.log('1'); - await stakingContract.connect(userB).delegate(poolAddr, { value: 100 }); - console.log('2'); - await stakingContract.connect(userA).delegate(otherPoolAddr, { value: 100 }); - console.log('3'); - await network.provider.send('evm_mine'); - console.log('===========>a', [ - await stakingContract.balanceOf(poolAddr, userA.address), - await stakingContract.totalBalance(poolAddr), - ]); - - console.log( - await ethers.provider.getBlockNumber(), - await validatorContract.periodOf(await ethers.provider.getBlockNumber()) - ); - - await validatorContract.connect(validatorCandidates[0]).depositReward({ value: 1000 }); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - console.log( - 0, - await ethers.provider.getBlockNumber(), - await validatorContract.periodOf(await ethers.provider.getBlockNumber()) - ); - - await validatorContract.connect(validatorCandidates[1]).depositReward({ value: 1000 }); - await network.provider.send('evm_mine'); - console.log( - 1, - await ethers.provider.getBlockNumber(), - await validatorContract.periodOf(await ethers.provider.getBlockNumber()) - ); - await network.provider.send('evm_mine'); - console.log( - 2, - await ethers.provider.getBlockNumber(), - await validatorContract.periodOf(await ethers.provider.getBlockNumber()) - ); - await network.provider.send('evm_mine'); - console.log( - 3, - await ethers.provider.getBlockNumber(), - await validatorContract.periodOf(await ethers.provider.getBlockNumber()) - ); - await local.recordReward(1000); - await expectLocalCalculationRight(); - console.log( - 4, - await ethers.provider.getBlockNumber(), - await validatorContract.periodOf(await ethers.provider.getBlockNumber()) - ); - - await stakingContract.connect(userA).delegate(poolAddr, { value: 100 }); - await network.provider.send('evm_mine'); - console.log( - 5, - await ethers.provider.getBlockNumber(), - await validatorContract.periodOf(await ethers.provider.getBlockNumber()) - ); - await expectLocalCalculationRight(); - - await validatorContract.connect(validatorCandidates[0]).depositReward({ value: 1000 }); - await network.provider.send('evm_mine'); - console.log( - 6, - await ethers.provider.getBlockNumber(), - await validatorContract.periodOf(await ethers.provider.getBlockNumber()) - ); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await stakingContract.connect(userA).undelegate(poolAddr, 200); - await validatorContract.connect(validatorCandidates[0]).depositReward({ value: 1000 }); - await network.provider.send('evm_mine'); - console.log( - 7, - await ethers.provider.getBlockNumber(), - await validatorContract.periodOf(await ethers.provider.getBlockNumber()) - ); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - // await stakingContract.stake(userA.address, 200); - await network.provider.send('evm_mine'); - console.log( - 8, - await ethers.provider.getBlockNumber(), - await validatorContract.periodOf(await ethers.provider.getBlockNumber()) - ); - await local.recordReward(0); - await expectLocalCalculationRight(); - - await validatorContract.settledReward([poolAddr, otherPoolAddr]); - // await stakingContract.endPeriod(); - await network.provider.send('evm_mine'); - console.log( - 9, - await ethers.provider.getBlockNumber(), - await validatorContract.periodOf(await ethers.provider.getBlockNumber()) - ); - console.log(local); - - local.commitRewardPool(); // ?? WHY: still right - console.log(local); - await expectLocalCalculationRight(); - - await network.provider.send('evm_mine'); - console.log( - 9, - await ethers.provider.getBlockNumber(), - await validatorContract.periodOf(await ethers.provider.getBlockNumber()) - ); + describe('Delegator test', () => { + // TODO }); - // it('Should work properly with staking actions occurring sequentially for a slashed period', async () => { - // await stakingContract.stake(userA.address, 100); - // await network.provider.send('evm_mine'); - // await expectLocalCalculationRight(); - // await local.recordReward(0); - - // await stakingContract.recordReward(1000); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // await expectLocalCalculationRight(); - - // await stakingContract.recordReward(1000); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // await expectLocalCalculationRight(); - - // await stakingContract.stake(userA.address, 300); - // await stakingContract.recordReward(1000); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // await expectLocalCalculationRight(); - - // await stakingContract.slash(); - // await network.provider.send('evm_mine'); - // local.slash(); - // await expectLocalCalculationRight(); - - // await stakingContract.recordReward(0); - // await network.provider.send('evm_mine'); - // await local.recordReward(0); - // await expectLocalCalculationRight(); - - // await network.provider.send('evm_mine'); - // await network.provider.send('evm_mine'); - // await network.provider.send('evm_mine'); - // await network.provider.send('evm_mine'); - - // await stakingContract.unstake(userA.address, 300); - // await network.provider.send('evm_mine'); - // await stakingContract.unstake(userA.address, 100); - // await network.provider.send('evm_mine'); - // await expectLocalCalculationRight(); - - // await stakingContract.commitRewardPool(); - // await stakingContract.endPeriod(); - // await network.provider.send('evm_mine'); - // await expectLocalCalculationRight(); - // }); - - // it('Should work properly with staking actions occurring sequentially for a slashed period again', async () => { - // await stakingContract.stake(userA.address, 100); - // await network.provider.send('evm_mine'); - // await local.recordReward(0); - // await expectLocalCalculationRight(); - - // await stakingContract.claimReward(userA.address); - // await stakingContract.recordReward(1000); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // local.claimRewardForA(); - // await expectLocalCalculationRight(); - - // await stakingContract.recordReward(1000); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // await expectLocalCalculationRight(); - - // await stakingContract.stake(userA.address, 300); - // await stakingContract.recordReward(1000); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // await expectLocalCalculationRight(); - - // await stakingContract.slash(); - // await network.provider.send('evm_mine'); - // local.slash(); - // await expectLocalCalculationRight(); - - // await stakingContract.recordReward(0); - // await network.provider.send('evm_mine'); - // await local.recordReward(0); - // await expectLocalCalculationRight(); - - // await network.provider.send('evm_mine'); - // await network.provider.send('evm_mine'); - // await network.provider.send('evm_mine'); - // await network.provider.send('evm_mine'); - - // await stakingContract.unstake(userA.address, 300); - // await network.provider.send('evm_mine'); - // await stakingContract.unstake(userA.address, 100); - // await network.provider.send('evm_mine'); - // await expectLocalCalculationRight(); - - // await stakingContract.claimReward(userB.address); - // await stakingContract.commitRewardPool(); - // await stakingContract.endPeriod(); - // await network.provider.send('evm_mine'); - // local.claimRewardForB(); - // await expectLocalCalculationRight(); - // }); - - // it('Should be able to calculate right reward after claiming', async () => { - // await stakingContract.recordReward(1000); - // await stakingContract.commitRewardPool(); - // await stakingContract.endPeriod(); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // local.commitRewardPool(); - // await expectLocalCalculationRight(); - - // await stakingContract.claimReward(userA.address); - // await stakingContract.recordReward(1000); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // local.claimRewardForA(); - // await expectLocalCalculationRight(); - - // await stakingContract.claimReward(userA.address); - // await network.provider.send('evm_mine'); - // local.claimRewardForA(); - // await expectLocalCalculationRight(); - - // await stakingContract.claimReward(userB.address); - // await stakingContract.commitRewardPool(); - // await stakingContract.endPeriod(); - // await network.provider.send('evm_mine'); - // local.claimRewardForB(); - // local.commitRewardPool(); - // await expectLocalCalculationRight(); - // }); - - // it('Should work properly with staking actions from multi-users occurring in the same block', async () => { - // await stakingContract.stake(userA.address, 100); - // await network.provider.send('evm_mine'); - - // await stakingContract.stake(userA.address, 300); - // await stakingContract.recordReward(1000); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // await expectLocalCalculationRight(); - - // await stakingContract.stake(userB.address, 200); - // await stakingContract.recordReward(1000); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // await expectLocalCalculationRight(); - - // await stakingContract.recordReward(1000); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // await expectLocalCalculationRight(); - - // await stakingContract.recordReward(1000); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // await expectLocalCalculationRight(); - - // await stakingContract.unstake(userB.address, 200); - // await stakingContract.unstake(userA.address, 400); - // await stakingContract.recordReward(1000); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // await expectLocalCalculationRight(); - - // await stakingContract.claimReward(userA.address); - // await stakingContract.claimReward(userB.address); - // await network.provider.send('evm_mine'); - // local.claimRewardForA(); - // local.claimRewardForB(); - // await expectLocalCalculationRight(); - - // await stakingContract.unstake(userA.address, 200); - // await stakingContract.commitRewardPool(); - // await stakingContract.endPeriod(); - // await network.provider.send('evm_mine'); - // local.commitRewardPool(); - // await expectLocalCalculationRight(); - - // await stakingContract.claimReward(userA.address); - // await stakingContract.claimReward(userB.address); - // await network.provider.send('evm_mine'); - // local.claimRewardForA(); - // local.claimRewardForB(); - // await expectLocalCalculationRight(); - // }); - - // it('Should work properly with staking actions occurring in the same block', async () => { - // await stakingContract.stake(userA.address, 100); - // await stakingContract.unstake(userA.address, 100); - // await stakingContract.stake(userA.address, 100); - // await stakingContract.unstake(userA.address, 100); - // await stakingContract.stake(userB.address, 200); - // await stakingContract.unstake(userB.address, 200); - // await stakingContract.stake(userB.address, 200); - // await stakingContract.unstake(userB.address, 200); - // await stakingContract.stake(userB.address, 200); - // await stakingContract.stake(userA.address, 100); - // await stakingContract.unstake(userA.address, 100); - // await stakingContract.unstake(userB.address, 200); - // await stakingContract.stake(userB.address, 200); - // await stakingContract.unstake(userA.address, 100); - // await stakingContract.stake(userA.address, 100); - // await stakingContract.unstake(userB.address, 200); - // await stakingContract.recordReward(1000); - // await stakingContract.commitRewardPool(); - // await stakingContract.endPeriod(); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // local.commitRewardPool(); - // await expectLocalCalculationRight(); - - // await stakingContract.recordReward(1000); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // await expectLocalCalculationRight(); - - // await stakingContract.slash(); - // await network.provider.send('evm_mine'); - // local.slash(); - // await expectLocalCalculationRight(); - - // await stakingContract.commitRewardPool(); - // await stakingContract.endPeriod(); - // await network.provider.send('evm_mine'); - // await expectLocalCalculationRight(); - - // await stakingContract.recordReward(1000); - // await stakingContract.commitRewardPool(); - // await stakingContract.endPeriod(); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // local.commitRewardPool(); - // await expectLocalCalculationRight(); - - // await stakingContract.recordReward(1000); - // await stakingContract.commitRewardPool(); - // await stakingContract.endPeriod(); - // await network.provider.send('evm_mine'); - // await local.recordReward(1000); - // local.commitRewardPool(); - // await expectLocalCalculationRight(); - - // await stakingContract.claimReward(userA.address); - // await stakingContract.claimReward(userB.address); - // await stakingContract.claimReward(userA.address); - // await stakingContract.claimReward(userB.address); - // await stakingContract.claimReward(userA.address); - // await stakingContract.claimReward(userB.address); - // await stakingContract.claimReward(userA.address); - // await stakingContract.claimReward(userA.address); - // await stakingContract.claimReward(userA.address); - // await stakingContract.claimReward(userB.address); - // await stakingContract.claimReward(userB.address); - // await stakingContract.claimReward(userB.address); - // await network.provider.send('evm_mine'); - // local.claimRewardForA(); - // local.claimRewardForB(); - // await expectLocalCalculationRight(); - // }); + describe('Reward Calculation test', () => { + it('Should work properly with staking actions occurring sequentially for a normal period', async () => { + await stakingContract.connect(userA).delegate(poolAddr.address, { value: 100 }); + await stakingContract.connect(userB).delegate(poolAddr.address, { value: 100 }); + await stakingContract.connect(userA).delegate(otherPoolAddr.address, { value: 100 }); + await network.provider.send('evm_mine'); + + await validatorContract.connect(poolAddr).depositReward({ value: 1000 }); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await validatorContract.connect(poolAddr).depositReward({ value: 1000 }); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await stakingContract.connect(userA).delegate(poolAddr.address, { value: 200 }); + await network.provider.send('evm_mine'); + await expectLocalCalculationRight(); + + await validatorContract.connect(poolAddr).depositReward({ value: 1000 }); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + console.log(local); + + await stakingContract.connect(userA).undelegate(poolAddr.address, 200); + await validatorContract.connect(poolAddr).depositReward({ value: 1000 }); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + console.log(local); + await expectLocalCalculationRight(); + + await stakingContract.connect(userA).delegate(poolAddr.address, { value: 200 }); + await network.provider.send('evm_mine'); + await local.recordReward(0); + await expectLocalCalculationRight(); + + await validatorContract.settledReward([poolAddr.address, otherPoolAddr.address]); + await validatorContract.endPeriod(); + await network.provider.send('evm_mine'); + local.commitRewardPool(); + await expectLocalCalculationRight(); + }); + + it('Should work properly with staking actions occurring sequentially for a slashed period', async () => { + await stakingContract.connect(userA).delegate(poolAddr.address, { value: 100 }); + await network.provider.send('evm_mine'); + await expectLocalCalculationRight(); + + await validatorContract.connect(poolAddr).depositReward({ value: 1000 }); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await validatorContract.connect(poolAddr).depositReward({ value: 1000 }); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await stakingContract.connect(userA).delegate(poolAddr.address, { value: 300 }); + await validatorContract.connect(poolAddr).depositReward({ value: 1000 }); + await network.provider.send('evm_mine'); + await local.recordReward(1000); + await expectLocalCalculationRight(); + + await validatorContract.slashMisdemeanor(poolAddr.address); + await network.provider.send('evm_mine'); + local.slash(); + await expectLocalCalculationRight(); + + await validatorContract.connect(poolAddr).depositReward({ value: 0 }); + await network.provider.send('evm_mine'); + await local.recordReward(0); + await expectLocalCalculationRight(); + + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + await network.provider.send('evm_mine'); + + await stakingContract.connect(userA).undelegate(poolAddr.address, 300); + await network.provider.send('evm_mine'); + await stakingContract.connect(userA).undelegate(poolAddr.address, 100); + await network.provider.send('evm_mine'); + await expectLocalCalculationRight(); + + await validatorContract.settledReward([poolAddr.address]); + await validatorContract.endPeriod(); + await network.provider.send('evm_mine'); + await expectLocalCalculationRight(); + }); + }); }); From 1f47ce18fe381696db6b8edcc3de937e7912970d Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Mon, 5 Sep 2022 22:03:31 +0700 Subject: [PATCH 060/190] [Staking] remove console.log & update test --- contracts/staking/DPoStaking.sol | 8 --- contracts/staking/StakingManager.sol | 67 +++++++++++------------ test/staking/DPoStaking.test.ts | 81 +++++++++++++++++++++++----- 3 files changed, 97 insertions(+), 59 deletions(-) diff --git a/contracts/staking/DPoStaking.sol b/contracts/staking/DPoStaking.sol index b6103b80b..ce64043fd 100644 --- a/contracts/staking/DPoStaking.sol +++ b/contracts/staking/DPoStaking.sol @@ -134,14 +134,7 @@ contract DPoStaking is IStaking, StakingManager, Initializable { * @inheritdoc IStaking */ function recordReward(address _consensusAddr, uint256 _reward) external payable onlyValidatorContract { - console.log( - "*** =>>>>>>> recordReward", - _reward, - _pendingPool[_consensusAddr].accumulatedRps, - totalBalance(_consensusAddr) - ); _recordReward(_consensusAddr, _reward); - console.log("*** =>>>>>>> recordReward", _pendingPool[_consensusAddr].accumulatedRps); } /** @@ -199,7 +192,6 @@ contract DPoStaking is IStaking, StakingManager, Initializable { returns (ValidatorCandidate storage _candidate) { uint256 _idx = _candidateIndex[_consensusAddr]; - console.log("_getCandidate:", _consensusAddr, _idx, ~_idx); require(_idx > 0, "DPoStaking: query for nonexistent candidate"); _candidate = validatorCandidates[~_idx]; } diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index c55be0454..889102232 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.9; -import "hardhat/console.sol"; import "../interfaces/IStaking.sol"; import "./RewardCalculation.sol"; @@ -17,7 +16,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { modifier notCandidateOwner(address _consensusAddr) { ValidatorCandidate memory _candidate = _getCandidate(_consensusAddr); - require(msg.sender != _candidate.candidateAdmin, "StakingManager: use unstake method instead"); + require(msg.sender != _candidate.candidateAdmin, "StakingManager: method caller is the candidate admin"); _; } @@ -55,10 +54,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { uint256 _amount = msg.value; address _stakingAddr = msg.sender; _candidateIdx = _proposeValidator(_consensusAddr, _treasuryAddr, _commissionRate, _amount, _stakingAddr); - console.log("_proposeValidator: done"); - console.log("_stake: bf", _candidateIdx); _stake(_consensusAddr, _stakingAddr, _amount); - console.log("_stake: af"); } /** @@ -139,7 +135,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { uint256 _amount ) internal { ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); - require(_candidate.candidateAdmin == _user, "StakingManager: invalid staking address"); + require(_candidate.candidateAdmin == _user, "StakingManager: user is not the candidate admin"); _candidate.stakedAmount += _amount; emit Staked(_poolAddr, _amount); @@ -162,13 +158,13 @@ abstract contract StakingManager is IStaking, RewardCalculation { uint256 _amount ) internal { ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); - require(_candidate.candidateAdmin == _user, "StakingManager: invalid staking address"); + require(_candidate.candidateAdmin == _user, "StakingManager: user is not the candidate admin"); require(_amount < _candidate.stakedAmount, "StakingManager: insufficient staked amount"); uint256 remainAmount = _candidate.stakedAmount - _amount; require(remainAmount >= minValidatorBalance(), "StakingManager: invalid staked amount left"); - _candidate.stakedAmount = _amount; + _candidate.stakedAmount -= _amount; emit Unstaked(_poolAddr, _amount); _undelegate(_poolAddr, _user, _amount); @@ -265,7 +261,6 @@ abstract contract StakingManager is IStaking, RewardCalculation { uint256 _amount ) internal { uint256 _newBalance = _delegatedAmount[_poolAddr][_user] + _amount; - console.log("_delegate", _poolAddr, _user, _newBalance); _syncUserReward(_poolAddr, _user, _newBalance); ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); @@ -274,32 +269,6 @@ abstract contract StakingManager is IStaking, RewardCalculation { emit Delegated(_user, _poolAddr, _amount); } - /** - * @dev Claims rewards from the pools `_poolAddrList`. - * - *@notice This function does not transfer reward to user. - * - * TODO: Check whether pool addr is in the candidate list. or add test for this fn. - * - */ - function _claimRewards(address _user, address[] calldata _poolAddrList) internal returns (uint256 _amount) { - for (uint256 _i = 0; _i < _poolAddrList.length; _i++) { - _amount += _claimReward(_poolAddrList[_i], _user); - } - } - - /** - * @dev Claims all pending rewards and delegates them to the consensus address. - */ - function _delegateRewards( - address _user, - address[] calldata _poolAddrList, - address _poolAddrDst - ) internal returns (uint256 _amount) { - _amount = _claimRewards(_user, _poolAddrList); - _delegate(_poolAddrDst, _user, _amount); - } - /** * @dev Undelegates from a validator address. * @@ -316,8 +285,6 @@ abstract contract StakingManager is IStaking, RewardCalculation { address _user, uint256 _amount ) internal { - console.log("_undelegate", _poolAddr, _user); - console.log("_undelegate", _delegatedAmount[_poolAddr][_user], _amount); require(_delegatedAmount[_poolAddr][_user] >= _amount, "StakingManager: insufficient amount to undelegate"); uint256 _newBalance = _delegatedAmount[_poolAddr][_user] - _amount; @@ -329,6 +296,32 @@ abstract contract StakingManager is IStaking, RewardCalculation { emit Undelegated(_user, _poolAddr, _amount); } + /** + * @dev Claims rewards from the pools `_poolAddrList`. + * + *@notice This function does not transfer reward to user. + * + * TODO: Check whether pool addr is in the candidate list. or add test for this fn. + * + */ + function _claimRewards(address _user, address[] calldata _poolAddrList) internal returns (uint256 _amount) { + for (uint256 _i = 0; _i < _poolAddrList.length; _i++) { + _amount += _claimReward(_poolAddrList[_i], _user); + } + } + + /** + * @dev Claims all pending rewards and delegates them to the consensus address. + */ + function _delegateRewards( + address _user, + address[] calldata _poolAddrList, + address _poolAddrDst + ) internal returns (uint256 _amount) { + _amount = _claimRewards(_user, _poolAddrList); + _delegate(_poolAddrDst, _user, _amount); + } + /////////////////////////////////////////////////////////////////////////////////////// // HELPER FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// diff --git a/test/staking/DPoStaking.test.ts b/test/staking/DPoStaking.test.ts index dd5c8b1f8..91e976359 100644 --- a/test/staking/DPoStaking.test.ts +++ b/test/staking/DPoStaking.test.ts @@ -15,6 +15,7 @@ let deployer: SignerWithAddress; let proxyAdmin: SignerWithAddress; let userA: SignerWithAddress; let userB: SignerWithAddress; +let governanceAdmin: SignerWithAddress; let validatorContract: MockValidatorSetForStaking; let stakingContract: DPoStaking; let validatorCandidates: SignerWithAddress[]; @@ -86,12 +87,12 @@ const expectLocalCalculationRight = async () => { } }; -const minValidatorBalance = BigNumber.from(0); +const minValidatorBalance = BigNumber.from(2); describe('DPoStaking test', () => { before(async () => { - [deployer, proxyAdmin, userA, userB, ...validatorCandidates] = await ethers.getSigners(); - validatorCandidates = validatorCandidates.slice(0, 2); + [deployer, proxyAdmin, userA, userB, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + validatorCandidates = validatorCandidates.slice(0, 3); const nonce = await deployer.getTransactionCount(); const proxyContractAddress = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); validatorContract = await new MockValidatorSetForStaking__factory(deployer).deploy(proxyContractAddress, 10, 2); @@ -102,15 +103,13 @@ describe('DPoStaking test', () => { logicContract.interface.encodeFunctionData('initialize', [ 28800, validatorContract.address, - ethers.constants.AddressZero, + governanceAdmin.address, 50, minValidatorBalance, ]) ); stakingContract = DPoStaking__factory.connect(proxyContract.address, deployer); expect(proxyContractAddress.toLowerCase()).eq(proxyContract.address.toLowerCase()); - poolAddr = validatorCandidates[0]; - otherPoolAddr = validatorCandidates[1]; }); describe('Validator candidate test', () => { @@ -121,7 +120,7 @@ describe('DPoStaking test', () => { }); it('Should be able to propose validator with sufficient amount', async () => { - for (let i = 0; i < validatorCandidates.length; i++) { + for (let i = 1; i < validatorCandidates.length; i++) { const candidate = validatorCandidates[i]; await stakingContract.connect(candidate).proposeValidator( candidate.address, @@ -130,21 +129,77 @@ describe('DPoStaking test', () => { { value: minValidatorBalance } ); } - await network.provider.send('evm_setAutomine', [false]); + + poolAddr = validatorCandidates[1]; + otherPoolAddr = validatorCandidates[2]; + expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance); }); - it('Should not be able to call stake/unstake when the method is not the candidate owner', async () => {}); + it('Should not be able to stake with empty value', async () => { + await expect(stakingContract.stake(poolAddr.address, { value: 0 })).revertedWith( + 'StakingManager: query for empty value' + ); + }); - it('Should be able to stake/unstake as a validator', async () => {}); + it('Should not be able to call stake/unstake when the method is not the candidate admin', async () => { + await expect(stakingContract.stake(poolAddr.address, { value: 1 })).revertedWith( + 'StakingManager: user is not the candidate admin' + ); + await expect(stakingContract.unstake(poolAddr.address, 1)).revertedWith( + 'StakingManager: user is not the candidate admin' + ); + }); - it('Should be not able to unstake with the balance left is not larger than the minimum balance threshold', async () => {}); + it('Should be able to stake/unstake as a validator', async () => { + await stakingContract.connect(poolAddr).stake(poolAddr.address, { value: 1 }); + expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance.add(1)); + }); + + it('Should be not able to unstake with the balance left is not larger than the minimum balance threshold', async () => { + await expect(stakingContract.connect(poolAddr).unstake(poolAddr.address, 2)).revertedWith( + 'StakingManager: invalid staked amount left' + ); + }); }); describe('Delegator test', () => { - // TODO + it('Should not be able to delegate with empty value', async () => { + await expect(stakingContract.delegate(otherPoolAddr.address)).revertedWith( + 'StakingManager: query for empty value' + ); + }); + + it('Should not be able to delegate/undelegate when the method caller is the candidate owner', async () => { + await expect(stakingContract.connect(poolAddr).delegate(poolAddr.address, { value: 1 })).revertedWith( + 'StakingManager: method caller is the candidate admin' + ); + await expect(stakingContract.connect(poolAddr).undelegate(poolAddr.address, 1)).revertedWith( + 'StakingManager: method caller is the candidate admin' + ); + }); + + it('Should be able to delegate/undelegate', async () => { + await stakingContract.connect(userA).delegate(otherPoolAddr.address, { value: 1 }); + await stakingContract.connect(userB).delegate(otherPoolAddr.address, { value: 1 }); + expect(await stakingContract.totalBalance(otherPoolAddr.address)).eq(minValidatorBalance.add(2)); + await stakingContract.connect(userA).undelegate(otherPoolAddr.address, 1); + expect(await stakingContract.totalBalance(otherPoolAddr.address)).eq(minValidatorBalance.add(1)); + }); }); describe('Reward Calculation test', () => { + before(async () => { + poolAddr = validatorCandidates[0]; + await stakingContract.connect(governanceAdmin).setMinValidatorBalance(0); + await stakingContract.connect(poolAddr).proposeValidator(poolAddr.address, poolAddr.address, 0, { value: 0 }); + + await network.provider.send('evm_setAutomine', [false]); + }); + + after(async () => { + await network.provider.send('evm_setAutomine', [true]); + }); + it('Should work properly with staking actions occurring sequentially for a normal period', async () => { await stakingContract.connect(userA).delegate(poolAddr.address, { value: 100 }); await stakingContract.connect(userB).delegate(poolAddr.address, { value: 100 }); @@ -171,13 +226,11 @@ describe('DPoStaking test', () => { await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - console.log(local); await stakingContract.connect(userA).undelegate(poolAddr.address, 200); await validatorContract.connect(poolAddr).depositReward({ value: 1000 }); await network.provider.send('evm_mine'); await local.recordReward(1000); - console.log(local); await expectLocalCalculationRight(); await stakingContract.connect(userA).delegate(poolAddr.address, { value: 200 }); From ce376e6f5ccd30670a21aa0640f70f42ace92800 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Mon, 5 Sep 2022 22:48:24 +0700 Subject: [PATCH 061/190] [Staking] rename function --- contracts/interfaces/IRoninValidatorSet.sol | 2 +- contracts/interfaces/IStaking.sol | 12 ++-- contracts/mocks/staking/MockStaking.sol | 10 +-- .../staking/MockValidatorSetForStaking.sol | 10 +-- .../ronin-validator/RoninValidatorSet.sol | 61 +++++++++---------- contracts/staking/DPoStaking.sol | 27 +++----- contracts/staking/RewardCalculation.sol | 14 ++--- contracts/staking/Staking.sol | 6 +- contracts/validator/ValidatorSet.sol | 2 +- test/staking/CoreStaking.test.ts | 46 +++++++------- test/staking/DPoStaking.test.ts | 3 +- 11 files changed, 90 insertions(+), 103 deletions(-) diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index 9c237f89c..3a160d436 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -83,7 +83,7 @@ interface IRoninValidatorSet { function jailed(address[] memory) external view returns (bool[] memory); /** - * @dev Returns whether the incoming reward of the validators are dropped during the period. + * @dev Returns whether the incoming reward of the validators are sinked during the period. */ function noPendingReward(address[] memory, uint256 _period) external view returns (bool[] memory); diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index c690c61c2..9d05f0b10 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -114,13 +114,13 @@ interface IStaking { * * Emits the `PendingPoolUpdated` event. * - * @notice This method should not be called after the pending pool is dropped. + * @notice This method should not be called after the pending pool is sinked. * */ - function recordReward(address _consensusAddr, uint256 _reward) external payable; + function recordRewardForDelegators(address _consensusAddr, uint256 _reward) external payable; /** - * @dev Settles the pending pool and allocates rewards for the validator. + * @dev Settles the pending pool and allocates rewards for the pool `_consensusAddr`. * * Requirements: * - The method caller is validator contract. @@ -128,10 +128,10 @@ interface IStaking { * Emits the `SettledPoolUpdated` event. * */ - function settleRewardPool(address _consensusAddr) external; + function settleRewardPoolForDelegators(address _consensusAddr) external; /** - * @dev Handles when the pending reward pool of the validator is dropped. + * @dev Handles when the pending reward pool of the validator is sinked. * * Requirements: * - The method caller is validator contract. @@ -139,7 +139,7 @@ interface IStaking { * Emits the `PendingPoolUpdated` event. * */ - function onRewardDropped(address _consensusAddr) external; + function sinkPendingReward(address _consensusAddr) external; /** * @dev Deducts from staking amount of the validator `_consensusAddr` for `_amount`. diff --git a/contracts/mocks/staking/MockStaking.sol b/contracts/mocks/staking/MockStaking.sol index b5632b8a9..5152a2c9b 100644 --- a/contracts/mocks/staking/MockStaking.sol +++ b/contracts/mocks/staking/MockStaking.sol @@ -42,11 +42,11 @@ contract MockStaking is RewardCalculation { function slash() external { uint256 _period = getPeriod(); _periodSlashed[_period] = true; - _onRewardDropped(poolAddr); + _sinkPendingReward(poolAddr); } - function recordReward(uint256 _rewardAmount) external { - _recordReward(poolAddr, _rewardAmount); + function recordRewardForDelegators(uint256 _rewardAmount) external { + _recordRewardForDelegators(poolAddr, _rewardAmount); } function commitRewardPool() external { @@ -54,7 +54,7 @@ contract MockStaking is RewardCalculation { } function increaseAccumulatedRps(uint256 _amount) external { - _recordReward(poolAddr, _amount); + _recordRewardForDelegators(poolAddr, _amount); } function getPeriod() public view returns (uint256) { @@ -73,7 +73,7 @@ contract MockStaking is RewardCalculation { return _totalBalance; } - function _rewardDropped(address, uint256 _period) internal view override returns (bool) { + function _rewardSinked(address, uint256 _period) internal view override returns (bool) { return _periodSlashed[_period]; } diff --git a/contracts/mocks/staking/MockValidatorSetForStaking.sol b/contracts/mocks/staking/MockValidatorSetForStaking.sol index c3e817cbc..420f08182 100644 --- a/contracts/mocks/staking/MockValidatorSetForStaking.sol +++ b/contracts/mocks/staking/MockValidatorSetForStaking.sol @@ -25,26 +25,26 @@ contract MockValidatorSetForStaking is IValidatorSet { } function depositReward() external payable override { - stakingContract.recordReward{ value: msg.value }(msg.sender, msg.value); + stakingContract.recordRewardForDelegators{ value: msg.value }(msg.sender, msg.value); } function settledReward(address[] memory _validatorList) external { for (uint _i = 0; _i < _validatorList.length; _i++) { - stakingContract.settleRewardPool(_validatorList[_i]); + stakingContract.settleRewardPoolForDelegators(_validatorList[_i]); } } function slashMisdemeanor(address _validator) external override { - stakingContract.onRewardDropped(_validator); + stakingContract.sinkPendingReward(_validator); } function slashFelony(address _validator) external override { - stakingContract.onRewardDropped(_validator); + stakingContract.sinkPendingReward(_validator); stakingContract.deductStakingAmount(_validator, 1); } function slashDoubleSign(address _validator) external override { - stakingContract.onRewardDropped(_validator); + stakingContract.sinkPendingReward(_validator); } function endPeriod() external { diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index b32af3ecd..cc89e0243 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -16,8 +16,6 @@ contract RoninValidatorSet is IRoninValidatorSet { address internal _slashIndicatorContract; // Change type to address for testing purpose /// @dev Staking contract address. address internal _stakingContract; // Change type to address for testing purpose - /// @dev The maximum number of validator. - uint256 internal _maxValidatorNumber; /// @dev The total of validators uint256 public validatorCount; @@ -25,6 +23,8 @@ contract RoninValidatorSet is IRoninValidatorSet { mapping(uint256 => address) internal _validator; /// @dev Mapping from validator address => bool mapping(address => bool) internal _validatorMap; + /// @dev The maximum number of validator. + uint256 internal _maxValidatorNumber; /// @dev Returns the number of epochs in a period uint256 internal _numberOfBlocksInEpoch; @@ -37,8 +37,10 @@ contract RoninValidatorSet is IRoninValidatorSet { mapping(uint256 => mapping(address => bool)) internal _noPendingReward; /// @dev Mapping from validator address => the last block that the validator is jailed mapping(address => uint256) internal _jailedUntil; - /// @dev Mapping from valdiator address => incoming reward amount - mapping(address => uint256) internal _pendingReward; + /// @dev Mapping from validator address => pending reward from producing block + mapping(address => uint256) internal _miningReward; + /// @dev Mapping from validator address => pending reward from delegating + mapping(address => uint256) internal _delegatingReward; /// @dev The amount of RON to slash felony. uint256 public slashFelonyAmount; @@ -72,27 +74,20 @@ contract RoninValidatorSet is IRoninValidatorSet { return; } - if (_jailed(_validatorAddr)) { + if (_jailed(_validatorAddr) || _noPendingReward[periodOf(block.number)][_validatorAddr]) { // TODO(Thor): emit the deprecated reward event return; } IStaking _staking = IStaking(_stakingContract); - // Assumes the staking contract returns the rate value in range [0; 100_00] uint256 _rate = _staking.commissionRateOf(_validatorAddr); - uint256 _mintingReward = (_rate * _reward) / 100_00; - - uint256 _amount = _reward - _mintingReward; - IStaking(_stakingContract).recordReward(_validatorAddr, _reward - _mintingReward); - // (1): We assume that we don't take back the reward recored for the delegator in the current epoch - // even when the validator is slashed. Therefore, we transfer we the RON amount to staking contract. - // TODO(Thor): use `call` to transfer reward with reentrancy gruard - require(payable(_stakingContract).send(_amount), "RoninValidatorSet: could not transfer RON"); - - if (!_noPendingReward[periodOf(block.number)][_validatorAddr]) { - _pendingReward[_validatorAddr] += _mintingReward; - // TODO(Thor): emit event - } + uint256 _miningAmount = (_rate * _reward) / 100_00; + uint256 _delegatingAmount = _reward - _miningAmount; + + _miningReward[_validatorAddr] += _miningAmount; + _delegatingReward[_validatorAddr] += _delegatingAmount; + IStaking(_stakingContract).recordRewardForDelegators(_validatorAddr, _delegatingAmount); + // TODO(Thor): emit event } /** @@ -103,21 +98,25 @@ contract RoninValidatorSet is IRoninValidatorSet { IStaking _staking = IStaking(_stakingContract); ISlashIndicator _slashIndicator = ISlashIndicator(_slashIndicatorContract); + address _validatorAddr; + uint256 _miningAmount; + uint256 _delegatingAmount; for (uint _i = 0; _i < validatorCount; _i++) { - address _validatorAddr = _validator[_i]; - // Same reason with (1), then we do not filter the slashed validator. - _staking.settleRewardPool(_validatorAddr); + _validatorAddr = _validator[_i]; _slashIndicator.resetCounter(_validatorAddr); - - uint256 _rewardAmount = _pendingReward[_validatorAddr]; + _miningAmount = _miningReward[_validatorAddr]; + _delegatingAmount = _delegatingReward[_validatorAddr]; if (!_jailed(_validatorAddr) && !_noPendingReward[periodOf(block.number)][_validatorAddr]) { // TODO(Thor): use `call` to transfer reward with reentrancy gruard require( - payable(_staking.treasuryAddressOf(_validatorAddr)).send(_rewardAmount), + payable(_staking.treasuryAddressOf(_validatorAddr)).send(_miningAmount), "RoninValidatorSet: could not transfer RON" ); + require(payable(address(_staking)).send(_delegatingAmount), "RoninValidatorSet: could not transfer RON"); + + _staking.settleRewardPoolForDelegators(_validatorAddr); } - _pendingReward[_validatorAddr] = 0; + _miningReward[_validatorAddr] = 0; } } _updateValidatorSet(); @@ -160,13 +159,9 @@ contract RoninValidatorSet is IRoninValidatorSet { */ function slashMisdemeanor(address _validatorAddr) public override { _noPendingReward[periodOf(block.number)][_validatorAddr] = true; - _pendingReward[_validatorAddr] = 0; - // We assume that current slashing won't effect to the recorded reward of the staking pool - // So we do not call the method `onRewardDropped` to the staking contract: - // ``` - // IStaking(_stakingContract).onRewardDropped(_validatorAddr); - //``` - // TODO(Thor): emit event + _miningReward[_validatorAddr] = 0; + _delegatingReward[_validatorAddr] = 0; + IStaking(_stakingContract).sinkPendingReward(_validatorAddr); } /** diff --git a/contracts/staking/DPoStaking.sol b/contracts/staking/DPoStaking.sol index ce64043fd..248c9f800 100644 --- a/contracts/staking/DPoStaking.sol +++ b/contracts/staking/DPoStaking.sol @@ -19,17 +19,12 @@ contract DPoStaking is IStaking, StakingManager, Initializable { /// @dev Validator contract address. address internal _validatorContract; // Change type to address for testing purpose - uint256[] internal currentValidatorIndexes; // TODO(Bao): leave comments for this variable - uint256 public numOfCabinets; // TODO(Bao): leave comments for this variable - /// @dev Configuration of number of blocks that validator has to wait before unstaking, counted from staking time - uint256 public unstakingOnHoldBlocksNum; // TODO(Bao): expose this fn in the interface - /// @dev Mapping from consensus address => bitwise negation of validator index in `validatorCandidates`. mapping(address => uint256) internal _candidateIndex; /// @dev The validator candidate array. ValidatorCandidate[] public validatorCandidates; - /// @dev Mapping from consensus address => period index => indicating the pending reward in the period is dropped or not. - mapping(address => mapping(uint256 => bool)) internal _pRewardDropped; + /// @dev Mapping from consensus address => period index => indicating the pending reward in the period is sinked or not. + mapping(address => mapping(uint256 => bool)) internal _pRewardSinked; modifier onlyGovernanceAdminContract() { require(msg.sender == _governanceAdminContract, "DPoStaking: method caller is not governance admin contract"); @@ -53,13 +48,11 @@ contract DPoStaking is IStaking, StakingManager, Initializable { * @dev Initializes the contract storage. */ function initialize( - uint256 _unstakingOnHoldBlocksNum, address __validatorContract, address __governanceAdminContract, uint256 __maxValidatorCandidate, uint256 __minValidatorBalance ) external initializer { - unstakingOnHoldBlocksNum = _unstakingOnHoldBlocksNum; _setValidatorContract(__validatorContract); _setGovernanceAdminContractAddress(__governanceAdminContract); _setMaxValidatorCandidate(__maxValidatorCandidate); @@ -133,24 +126,24 @@ contract DPoStaking is IStaking, StakingManager, Initializable { /** * @inheritdoc IStaking */ - function recordReward(address _consensusAddr, uint256 _reward) external payable onlyValidatorContract { - _recordReward(_consensusAddr, _reward); + function recordRewardForDelegators(address _consensusAddr, uint256 _reward) external payable onlyValidatorContract { + _recordRewardForDelegators(_consensusAddr, _reward); } /** * @inheritdoc IStaking */ - function settleRewardPool(address _consensusAddr) external onlyValidatorContract { + function settleRewardPoolForDelegators(address _consensusAddr) external onlyValidatorContract { _onPoolSettled(_consensusAddr); } /** * @inheritdoc IStaking */ - function onRewardDropped(address _consensusAddr) external { + function sinkPendingReward(address _consensusAddr) external { uint256 _period = _periodOf(block.number); - _pRewardDropped[_consensusAddr][_period] = true; - _onRewardDropped(_consensusAddr); + _pRewardSinked[_consensusAddr][_period] = true; + _sinkPendingReward(_consensusAddr); } /** @@ -267,8 +260,8 @@ contract DPoStaking is IStaking, StakingManager, Initializable { /** * @inheritdoc RewardCalculation */ - function _rewardDropped(address _poolAddr, uint256 _period) internal view virtual override returns (bool) { - return _pRewardDropped[_poolAddr][_period]; + function _rewardSinked(address _poolAddr, uint256 _period) internal view virtual override returns (bool) { + return _pRewardSinked[_poolAddr][_period]; } /** diff --git a/contracts/staking/RewardCalculation.sol b/contracts/staking/RewardCalculation.sol index f4f91ba86..04dbfa803 100644 --- a/contracts/staking/RewardCalculation.sol +++ b/contracts/staking/RewardCalculation.sol @@ -80,7 +80,7 @@ abstract contract RewardCalculation { PendingPool memory _pool = _pendingPool[_poolAddr]; uint256 _balance = balanceOf(_poolAddr, _user); - if (_rewardDropped(_poolAddr, _periodOf(_reward.lastSyncBlock))) { + if (_rewardSinked(_poolAddr, _periodOf(_reward.lastSyncBlock))) { SettledRewardFields memory _sReward = _sUserReward[_poolAddr][_user]; uint256 _credited = (_sReward.accumulatedRps * _balance) / 1e18; return (_balance * _pool.accumulatedRps) / 1e18 + _sReward.debited - _credited; @@ -194,10 +194,10 @@ abstract contract RewardCalculation { * * Emits the `PendingPoolUpdated` event. * - * @notice This method should not be called after the pending pool is dropped. + * @notice This method should not be called after the pending pool is sinked. * */ - function _recordReward(address _poolAddr, uint256 _reward) internal { + function _recordRewardForDelegators(address _poolAddr, uint256 _reward) internal { PendingPool storage _pool = _pendingPool[_poolAddr]; uint256 _accumulatedRps = _pool.accumulatedRps + (_reward * 1e18) / totalBalance(_poolAddr); _pool.accumulatedRps = _accumulatedRps; @@ -206,14 +206,14 @@ abstract contract RewardCalculation { } /** - * @dev Handles when the pool `_poolAddr` is dropped. + * @dev Handles when the pool `_poolAddr` is sinked. * * Emits the `PendingPoolUpdated` event. * - * @notice This method should be called when the pool is dropped. + * @notice This method should be called when the pool is sinked. * */ - function _onRewardDropped(address _poolAddr) internal { + function _sinkPendingReward(address _poolAddr) internal { uint256 _accumulatedRps = _settledPool[_poolAddr].accumulatedRps; PendingPool storage _pool = _pendingPool[_poolAddr]; _pool.accumulatedRps = _accumulatedRps; @@ -240,7 +240,7 @@ abstract contract RewardCalculation { /** * @dev Returns whether the pool is slashed in the period `_period`. */ - function _rewardDropped(address _poolAddr, uint256 _period) internal view virtual returns (bool); + function _rewardSinked(address _poolAddr, uint256 _period) internal view virtual returns (bool); /** * @dev Returns the period from the block number. diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 62c4f2b2b..9ddec963a 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -491,11 +491,11 @@ contract Staking is IStaking, Initializable { function getValidatorCandidates() external view override returns (ValidatorCandidate[] memory candidates) {} - function recordReward(address _consensusAddr, uint256 _reward) external payable override {} + function recordRewardForDelegators(address _consensusAddr, uint256 _reward) external payable override {} - function settleRewardPool(address _consensusAddr) external override {} + function settleRewardPoolForDelegators(address _consensusAddr) external override {} - function onRewardDropped(address _consensusAddr) external override {} + function sinkPendingReward(address _consensusAddr) external override {} function deductStakingAmount(address _consensusAddr, uint256 _amount) external override {} diff --git a/contracts/validator/ValidatorSet.sol b/contracts/validator/ValidatorSet.sol index dd66bc350..815b5444d 100644 --- a/contracts/validator/ValidatorSet.sol +++ b/contracts/validator/ValidatorSet.sol @@ -93,7 +93,7 @@ contract ValidatorSet is IValidatorSet, ValidatorSetCore { if (_validator.jailed) { emit DeprecatedDeposit(_valAddr, _value); } else { - stakingContract.recordReward(_valAddr, _value); + stakingContract.recordRewardForDelegators(_valAddr, _value); } } diff --git a/test/staking/CoreStaking.test.ts b/test/staking/CoreStaking.test.ts index 9f3187863..2e90c684a 100644 --- a/test/staking/CoreStaking.test.ts +++ b/test/staking/CoreStaking.test.ts @@ -93,12 +93,12 @@ describe('Core Staking test', () => { await stakingContract.stake(userB.address, 100); await network.provider.send('evm_mine'); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await network.provider.send('evm_mine'); await network.provider.send('evm_mine'); @@ -109,13 +109,13 @@ describe('Core Staking test', () => { await network.provider.send('evm_mine'); await expectLocalCalculationRight(); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); await stakingContract.unstake(userA.address, 200); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); @@ -138,18 +138,18 @@ describe('Core Staking test', () => { await expectLocalCalculationRight(); await local.recordReward(0); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); await stakingContract.stake(userA.address, 300); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); @@ -159,7 +159,7 @@ describe('Core Staking test', () => { local.slash(); await expectLocalCalculationRight(); - await stakingContract.recordReward(0); + await stakingContract.recordRewardForDelegators(0); await network.provider.send('evm_mine'); await local.recordReward(0); await expectLocalCalculationRight(); @@ -188,19 +188,19 @@ describe('Core Staking test', () => { await expectLocalCalculationRight(); await stakingContract.claimReward(userA.address); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); local.claimRewardForA(); await expectLocalCalculationRight(); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); await stakingContract.stake(userA.address, 300); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); @@ -210,7 +210,7 @@ describe('Core Staking test', () => { local.slash(); await expectLocalCalculationRight(); - await stakingContract.recordReward(0); + await stakingContract.recordRewardForDelegators(0); await network.provider.send('evm_mine'); await local.recordReward(0); await expectLocalCalculationRight(); @@ -235,7 +235,7 @@ describe('Core Staking test', () => { }); it('Should be able to calculate right reward after claiming', async () => { - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await stakingContract.commitRewardPool(); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); @@ -244,7 +244,7 @@ describe('Core Staking test', () => { await expectLocalCalculationRight(); await stakingContract.claimReward(userA.address); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); local.claimRewardForA(); @@ -269,30 +269,30 @@ describe('Core Staking test', () => { await network.provider.send('evm_mine'); await stakingContract.stake(userA.address, 300); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); await stakingContract.stake(userB.address, 200); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); await stakingContract.unstake(userB.address, 200); await stakingContract.unstake(userA.address, 400); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); @@ -336,7 +336,7 @@ describe('Core Staking test', () => { await stakingContract.unstake(userA.address, 100); await stakingContract.stake(userA.address, 100); await stakingContract.unstake(userB.address, 200); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await stakingContract.commitRewardPool(); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); @@ -344,7 +344,7 @@ describe('Core Staking test', () => { local.commitRewardPool(); await expectLocalCalculationRight(); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); @@ -359,7 +359,7 @@ describe('Core Staking test', () => { await network.provider.send('evm_mine'); await expectLocalCalculationRight(); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await stakingContract.commitRewardPool(); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); @@ -367,7 +367,7 @@ describe('Core Staking test', () => { local.commitRewardPool(); await expectLocalCalculationRight(); - await stakingContract.recordReward(1000); + await stakingContract.recordRewardForDelegators(1000); await stakingContract.commitRewardPool(); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); diff --git a/test/staking/DPoStaking.test.ts b/test/staking/DPoStaking.test.ts index 91e976359..c17c4da7a 100644 --- a/test/staking/DPoStaking.test.ts +++ b/test/staking/DPoStaking.test.ts @@ -101,7 +101,6 @@ describe('DPoStaking test', () => { logicContract.address, proxyAdmin.address, logicContract.interface.encodeFunctionData('initialize', [ - 28800, validatorContract.address, governanceAdmin.address, 50, @@ -132,7 +131,7 @@ describe('DPoStaking test', () => { poolAddr = validatorCandidates[1]; otherPoolAddr = validatorCandidates[2]; - expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance); + expect(await stakingContract.callStatic.totalBalance(poolAddr.address)).eq(minValidatorBalance); }); it('Should not be able to stake with empty value', async () => { From fb384e44cfae9c7f6e38c68ae0bb6e36c1ed5298 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 6 Sep 2022 08:36:16 +0700 Subject: [PATCH 062/190] [SlashIndicator] update contract & its interface --- contracts/interfaces/ISlashIndicator.sol | 47 +++++--- contracts/slash/SlashIndicator.sol | 141 +++++++++-------------- 2 files changed, 81 insertions(+), 107 deletions(-) diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index 3fdfa82eb..03b2532ce 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -2,7 +2,13 @@ pragma solidity ^0.8.9; +import "../interfaces/IRoninValidatorSet.sol"; + interface ISlashIndicator { + // TODO: fill comment for event. IE: Emitted when... + event ValidatorSlashed(address indexed validator, SlashType slashType); + event UnavailabilityIndicatorReset(address indexed validator); + enum SlashType { UNKNOWN, MISDEMAENOR, @@ -18,55 +24,60 @@ interface ISlashIndicator { } /** - * @notice Slash for inavailability - * - * @dev Increase the counter of validator with valAddr. If the counter passes the threshold, call - * the function from Validators.sol + * @dev Returns the validator contract. + */ + function validatorContract() external view returns (IRoninValidatorSet); + + /** + * @dev Slashs for inavailability by increasing the counter of validator with `_valAddr`. + * If the counter passes the threshold, call the function from the validator contract. * * Requirements: * - Only coinbase can call this method * + * Emits the event `ValidatorSlashed`. + * */ - function slash(address valAddr) external; + function slash(address _valAddr) external; /** - * @dev Reset the counter of the validator at the end of every period + * @dev Resets the counter of the validator at the end of every period * * Requirements: * - Only validator contract can call this method + * + * Emits the event `UnavailabilityIndicatorReset`. + * */ function resetCounter(address) external; /** - * @dev Reset the counter of all validators at the end of every period + * @dev Resets the counter of all validators at the end of every period * * Requirements: * - Only validator contract can call this method + * + * Emits the `UnavailabilityIndicatorReset` events. + * */ function resetCounters(address[] calldata) external; /** - * @notice Slash for double signing - * - * @dev Verify the evidence, call the function from Validators.sol + * @dev Slashs for double signing. * * Requirements: * - Only coinbase can call this method * */ - function slashDoubleSign(address valAddr, bytes calldata evidence) external; - - /// - /// QUERY FUNCTIONS - /// + function slashDoubleSign(address _valAddr, bytes calldata _evidence) external; /** - * @notice Get slash indicator of a validator + * @dev Gets slash indicator of a validator. */ - function getSlashIndicator(address validator) external view returns (Indicator memory); + function getSlashIndicator(address _validator) external view returns (Indicator memory); /** - * @notice Get slash threshold + * @dev Gets slash threshold. */ function getSlashThresholds() external view returns (uint256 misdemeanorThreshold, uint256 felonyThreshold); } diff --git a/contracts/slash/SlashIndicator.sol b/contracts/slash/SlashIndicator.sol index 96138796c..a24c779e6 100644 --- a/contracts/slash/SlashIndicator.sol +++ b/contracts/slash/SlashIndicator.sol @@ -4,148 +4,111 @@ pragma solidity ^0.8.9; import "../interfaces/ISlashIndicator.sol"; import "../interfaces/IStaking.sol"; -import "../interfaces/IValidatorSet.sol"; +import "../interfaces/IRoninValidatorSet.sol"; +/** + * TODO: Apply proxy pattern to this contract. + */ contract SlashIndicator is ISlashIndicator { - /// Init configuration - uint256 public constant MISDEMEANOR_THRESHOLD = 50; - uint256 public constant FELONY_THRESHOLD = 150; - - /// State of the contract - bool public initialized; - address[] public validators; - mapping(address => Indicator) public indicators; - uint256 public previousHeight; - - /// Threshold of slashing - uint256 public misdemeanorThreshold; - uint256 public felonyThreshold; - - /// Other contract address - IValidatorSet public validatorSetContract; - IStaking public stakingContract; - - event SlashedValidator(address indexed validator, SlashType slashType); - event ResetIndicator(address indexed validator); - event ResetIndicators(); + /// @dev Mapping from validator address => unavailability indicator + mapping(address => Indicator) internal _unavailabilityIndicator; + /// @dev The last block that a validator is slashed + uint256 public lastSlashedBlock; + + /// @dev The threshold to slash when validator is unavailability reaches misdemeanor + uint256 public misdemeanorThreshold; // TODO: add setter by gov admin + /// @dev The threshold to slash when validator is unavailability reaches felony + uint256 public felonyThreshold; // TODO: add setter by gov admin + /// @dev The validator contract + IRoninValidatorSet public validatorContract; modifier onlyCoinbase() { - require(msg.sender == block.coinbase, "Slash: Only coinbase"); + require(msg.sender == block.coinbase, "SlashIndicator: method caller is not the coinbase"); _; } modifier onlyValidatorContract() { - require(msg.sender == address(validatorSetContract), "Slash: Only validator set contract"); + require(msg.sender == address(validatorContract), "SlashIndicator: method caller is not the validator contract"); _; } modifier oncePerBlock() { - require(block.number > previousHeight, "Slash: Cannot slash twice in one block"); - _; - previousHeight = block.number; - } - - modifier onlyInitialized() { - require(initialized, "Slash: Contract is not initialized"); + require(block.number > lastSlashedBlock, "SlashIndicator: cannot slash twice in one block"); _; + lastSlashedBlock = block.number; } - constructor() { - misdemeanorThreshold = MISDEMEANOR_THRESHOLD; - felonyThreshold = FELONY_THRESHOLD; - } - - function initialize(IValidatorSet _validatorSetContract, IStaking _stakingContract) external { - require(!initialized, "Slash: Contract is already initialized"); - - initialized = true; - validatorSetContract = _validatorSetContract; - stakingContract = _stakingContract; + constructor(IRoninValidatorSet _validatorSetContract) { + misdemeanorThreshold = 50; + felonyThreshold = 150; + validatorContract = _validatorSetContract; } /** - * @notice Slash for inavailability - * - * @dev Increase the counter of validator with valAddr. If the counter passes the threshold, call - * the function from Validators.sol - * - * Requirements: - * - Only coinbase can call this method - * + * @inheritdoc ISlashIndicator */ - function slash(address _validatorAddr) external override onlyInitialized onlyCoinbase oncePerBlock { - // Check if the to be slashed validator is in the current epoch - require( - validatorSetContract.isCurrentValidator(_validatorAddr), - "Slash: Cannot slash validator not in current epoch" - ); - - Indicator storage indicator = indicators[_validatorAddr]; - + function slash(address _validatorAddr) external override onlyCoinbase oncePerBlock { + Indicator storage indicator = _unavailabilityIndicator[_validatorAddr]; indicator.counter++; indicator.lastSyncedBlock = block.number; - // Slash the validator as either the fenoly or the misdemeanor + // Slashs the validator as either the fenoly or the misdemeanor if (indicator.counter == felonyThreshold) { - indicator.counter = 0; - validatorSetContract.slashFelony(_validatorAddr); - emit SlashedValidator(_validatorAddr, SlashType.FELONY); + validatorContract.slashFelony(_validatorAddr); + emit ValidatorSlashed(_validatorAddr, SlashType.FELONY); } else if (indicator.counter == misdemeanorThreshold) { - validatorSetContract.slashMisdemeanor(_validatorAddr); - emit SlashedValidator(_validatorAddr, SlashType.MISDEMAENOR); + validatorContract.slashMisdemeanor(_validatorAddr); + emit ValidatorSlashed(_validatorAddr, SlashType.MISDEMAENOR); } } /** - * @dev Reset the counter of the validator everyday - * - * Requirements: - * - Only validator contract can call this method + * @inheritdoc ISlashIndicator */ - function resetCounters(address[] calldata _validatorAddrs) external override onlyInitialized onlyValidatorContract { + function resetCounters(address[] calldata _validatorAddrs) external override onlyValidatorContract { if (_validatorAddrs.length == 0) { return; } - for (uint i = 0; i < _validatorAddrs.length; i++) { - _resetCounter(_validatorAddrs[i]); + for (uint256 _i; _i < _validatorAddrs.length; _i++) { + _resetCounter(_validatorAddrs[_i]); } - - emit ResetIndicators(); } - function resetCounter(address _validatorAddr) external override onlyInitialized onlyValidatorContract { + /** + * @inheritdoc ISlashIndicator + */ + function resetCounter(address _validatorAddr) external override onlyValidatorContract { _resetCounter(_validatorAddr); - emit ResetIndicator(_validatorAddr); + emit UnavailabilityIndicatorReset(_validatorAddr); } /** - * @notice Slash for double signing - * - * @dev Verify the evidence, call the function from Validators.sol - * - * Requirements: - * - Only coinbase can call this method - * + * @inheritdoc ISlashIndicator */ - function slashDoubleSign(address valAddr, bytes calldata evidence) external override onlyInitialized onlyCoinbase { + function slashDoubleSign(address _valAddr, bytes calldata _evidence) external override onlyCoinbase { revert("Not implemented"); } /** - * @notice Get slash indicator of a validator + * @inheritdoc ISlashIndicator */ - function getSlashIndicator(address validator) external view override returns (Indicator memory) { - Indicator memory _indicator = indicators[validator]; - return _indicator; + function getSlashIndicator(address validator) external view override returns (Indicator memory _indicator) { + _indicator = _unavailabilityIndicator[validator]; } + /** + * @inheritdoc ISlashIndicator + */ function getSlashThresholds() external view override returns (uint256, uint256) { return (misdemeanorThreshold, felonyThreshold); } + /** + * @dev Resets counter for the validator address. + */ function _resetCounter(address _validatorAddr) private { - Indicator storage _indicator = indicators[_validatorAddr]; + Indicator storage _indicator = _unavailabilityIndicator[_validatorAddr]; _indicator.counter = 0; _indicator.lastSyncedBlock = block.number; } From b2c7d1ae1a76f0f73ae23ad1d101dc51d1e76a09 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 6 Sep 2022 10:30:04 +0700 Subject: [PATCH 063/190] [RoninValidatorSet] fix delegating reward --- contracts/ronin-validator/RoninValidatorSet.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index cc89e0243..236e318d4 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -117,6 +117,8 @@ contract RoninValidatorSet is IRoninValidatorSet { _staking.settleRewardPoolForDelegators(_validatorAddr); } _miningReward[_validatorAddr] = 0; + _delegatingReward[_validatorAddr] = 0; + // TODO: emit event } } _updateValidatorSet(); From 34359d69e516832f57fbb64e34291347290c0c41 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 6 Sep 2022 13:08:53 +0700 Subject: [PATCH 064/190] [RoninValidatorSet] update wrap up epoch fn --- .../ronin-validator/RoninValidatorSet.sol | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index 236e318d4..ee404b63c 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -94,33 +94,38 @@ contract RoninValidatorSet is IRoninValidatorSet { * @inheritdoc IRoninValidatorSet */ function wrapUpEpoch() external payable override onlyCoinbase whenEndEpoch { - if (periodEnded(block.number)) { - IStaking _staking = IStaking(_stakingContract); - ISlashIndicator _slashIndicator = ISlashIndicator(_slashIndicatorContract); - - address _validatorAddr; - uint256 _miningAmount; - uint256 _delegatingAmount; - for (uint _i = 0; _i < validatorCount; _i++) { - _validatorAddr = _validator[_i]; - _slashIndicator.resetCounter(_validatorAddr); - _miningAmount = _miningReward[_validatorAddr]; - _delegatingAmount = _delegatingReward[_validatorAddr]; - if (!_jailed(_validatorAddr) && !_noPendingReward[periodOf(block.number)][_validatorAddr]) { + IStaking _staking = IStaking(_stakingContract); + ISlashIndicator _slashIndicator = ISlashIndicator(_slashIndicatorContract); + + address _validatorAddr; + for (uint _i = 0; _i < validatorCount; _i++) { + _validatorAddr = _validator[_i]; + _slashIndicator.resetCounter(_validatorAddr); + + if (!_jailed(_validatorAddr) && !_noPendingReward[periodOf(block.number)][_validatorAddr]) { + if (periodEnded(block.number)) { + uint256 _miningAmount = _miningReward[_validatorAddr]; + _miningReward[_validatorAddr] = 0; + if (_miningAmount > 0) { + // TODO(Thor): use `call` to transfer reward with reentrancy gruard + require( + payable(_staking.treasuryAddressOf(_validatorAddr)).send(_miningAmount), + "RoninValidatorSet: could not transfer RON" + ); + } + } + + uint256 _delegatingAmount = _delegatingReward[_validatorAddr]; + _delegatingReward[_validatorAddr] = 0; + if (_delegatingAmount > 0) { // TODO(Thor): use `call` to transfer reward with reentrancy gruard - require( - payable(_staking.treasuryAddressOf(_validatorAddr)).send(_miningAmount), - "RoninValidatorSet: could not transfer RON" - ); require(payable(address(_staking)).send(_delegatingAmount), "RoninValidatorSet: could not transfer RON"); - - _staking.settleRewardPoolForDelegators(_validatorAddr); } - _miningReward[_validatorAddr] = 0; - _delegatingReward[_validatorAddr] = 0; - // TODO: emit event + _staking.settleRewardPoolForDelegators(_validatorAddr); } + // TODO: emit event } + _updateValidatorSet(); } From ee4b910fef01b871797794bd934b7cd1fecdb449 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 6 Sep 2022 13:17:42 +0700 Subject: [PATCH 065/190] [Sorting] Add mock. Add test. --- contracts/mocks/sorting/MockSorting.sol | 44 ++++++++++++ test/sorting/QuickSort.test.ts | 90 +++++++++++++++++-------- 2 files changed, 105 insertions(+), 29 deletions(-) create mode 100644 contracts/mocks/sorting/MockSorting.sol diff --git a/contracts/mocks/sorting/MockSorting.sol b/contracts/mocks/sorting/MockSorting.sol new file mode 100644 index 000000000..c35810a86 --- /dev/null +++ b/contracts/mocks/sorting/MockSorting.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../../libraries/Sorting.sol"; + +contract MockSorting { + uint256[] public data; + + function addData(uint256[] memory _data) public { + for (uint256 i; i < _data.length; i++) { + data.push(_data[i]); + } + } + + function sort(uint256[] memory _data) public pure returns (uint256[] memory) { + return Sorting.sort(_data); + } + + function sortOnStorage() public returns (uint256[] memory, uint256) { + uint256[] memory _tmpData = data; + data = Sorting.sort(_tmpData); + + return (data, data.length); + } + + function copyStorage2Memory() public view returns (uint256[] memory, uint256) { + uint256[] memory _tmpData = new uint256[](data.length); + + for (uint256 i; i < data.length; i++) { + _tmpData[i] = data[i]; + } + + return (_tmpData, data.length); + } + + function sortAddressesAndValues(address[] calldata _addrs, uint256[] calldata _values) + public + pure + returns (address[] memory) + { + return Sorting.sort(_addrs, _values); + } +} diff --git a/test/sorting/QuickSort.test.ts b/test/sorting/QuickSort.test.ts index 4e235214f..723cb8c87 100644 --- a/test/sorting/QuickSort.test.ts +++ b/test/sorting/QuickSort.test.ts @@ -1,29 +1,61 @@ -// import { expect } from 'chai'; -// import { deployments, ethers } from 'hardhat'; -// import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -// import { solidityKeccak256 } from 'ethers/lib/utils'; - -// import { -// QuickSort, -// QuickSort__factory, -// QuickSortMock, -// QuickSortMock__factory -// } from '../../src/types'; -// import { BigNumber } from 'ethers/lib/ethers'; - -// let quickSortLib: QuickSort; -// let quickSortContract: QuickSortMock; - -// let admin: SignerWithAddress; - -// describe('Quick sort', () => { -// before(async () => { -// [admin] = await ethers.getSigners(); -// quickSortLib = await new QuickSort__factory().deploy(); -// quickSortContract = await new QuickSortMock__factory(admin).deploy(); -// }); - -// describe('Operation functions', async () => { - -// }); -// }); +import { expect } from 'chai'; +import { ethers } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; + +import { Sorting, Sorting__factory, MockSorting, MockSorting__factory } from '../../src/types'; + +let quickSortLib: Sorting; +let quickSortContract: MockSorting; + +let deployer: SignerWithAddress; +let signers: SignerWithAddress[]; + +const runSortWithNRecords = async (numOfRecords: number) => { + let balances = []; + for (let i = 0; i < numOfRecords; ++i) { + balances.push({ + address: signers[i].address, + value: Math.floor(Math.random() * numOfRecords * 1000), + }); + } + + balances.sort((a, b) => (a.value < b.value ? 1 : -1)); + + let sorted = await quickSortContract.sortAddressesAndValues( + balances.map((_) => _.address), + balances.map((_) => _.value) + ); + + expect(sorted).eql(balances.map((_) => _.address)); +}; + +describe('Quick sort', () => { + before(async () => { + [deployer, ...signers] = await ethers.getSigners(); + quickSortLib = await new Sorting__factory(deployer).deploy(); + quickSortContract = await new MockSorting__factory( + { + 'contracts/libraries/Sorting.sol:Sorting': quickSortLib.address, + }, + deployer + ).deploy(); + }); + + describe('Sorting on sort(address[], uint[])', async () => { + it('Should sort correctly on 10 records', async () => { + await runSortWithNRecords(10); + }); + + it('Should sort correctly on 21 records', async () => { + await runSortWithNRecords(21); + }); + + it('Should sort correctly on 50 records', async () => { + await runSortWithNRecords(50); + }); + + it('Should sort correctly on 99 records', async () => { + await runSortWithNRecords(99); + }); + }); +}); From 85c5b7bd0dfc05147e28d514c80bc30f1db8596d Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 6 Sep 2022 13:18:33 +0700 Subject: [PATCH 066/190] [RoninValidatorSet] Fix wrapUpEpoch to update balance after settling --- contracts/ronin-validator/RoninValidatorSet.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index ee404b63c..9879b584a 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -122,6 +122,8 @@ contract RoninValidatorSet is IRoninValidatorSet { require(payable(address(_staking)).send(_delegatingAmount), "RoninValidatorSet: could not transfer RON"); } _staking.settleRewardPoolForDelegators(_validatorAddr); + _miningReward[_validatorAddr] = 0; + _delegatingReward[_validatorAddr] = 0; } // TODO: emit event } @@ -308,6 +310,8 @@ contract RoninValidatorSet is IRoninValidatorSet { _candidates = Sorting.sort(_candidates, _weights); uint256 _newValidatorCount = Math.min(_maxValidatorNumber, _candidates.length); + // TODO: pick at least M governers as validators + for (uint256 _i = _newValidatorCount; _i < validatorCount; _i++) { delete _validator[_i]; delete _validatorMap[_validator[_i]]; From 55b4c5c49005d75a4f33299ef162e1a5184cd7a6 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 6 Sep 2022 13:19:06 +0700 Subject: [PATCH 067/190] [StakingManager] Update error messages --- contracts/staking/StakingManager.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 889102232..e5c4fb82c 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -10,13 +10,13 @@ abstract contract StakingManager is IStaking, RewardCalculation { mapping(address => mapping(address => uint256)) internal _delegatedAmount; modifier noEmptyValue() { - require(msg.value > 0, "StakingManager: query for empty value"); + require(msg.value > 0, "StakingManager: cannot deposit empty value"); _; } modifier notCandidateOwner(address _consensusAddr) { ValidatorCandidate memory _candidate = _getCandidate(_consensusAddr); - require(msg.sender != _candidate.candidateAdmin, "StakingManager: method caller is the candidate admin"); + require(msg.sender != _candidate.candidateAdmin, "StakingManager: method caller must not be the candidate admin"); _; } @@ -159,7 +159,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { ) internal { ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); require(_candidate.candidateAdmin == _user, "StakingManager: user is not the candidate admin"); - require(_amount < _candidate.stakedAmount, "StakingManager: insufficient staked amount"); + require(_amount <= _candidate.stakedAmount, "StakingManager: insufficient staked amount"); uint256 remainAmount = _candidate.stakedAmount - _amount; require(remainAmount >= minValidatorBalance(), "StakingManager: invalid staked amount left"); From e89557c3600187081120021a4bc78fe2cbda7d86 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 6 Sep 2022 14:29:46 +0700 Subject: [PATCH 068/190] Update test case --- contracts/libraries/Math.sol | 4 +- contracts/libraries/Sorting.sol | 10 ++-- contracts/mocks/sorting/MockSorting.sol | 10 ---- contracts/staking/StakingManager.sol | 2 +- src/deploy/staking.ts | 13 +---- test/sorting/MockSwapStorage.test.ts | 2 +- test/sorting/QuickSort.test.ts | 58 +++++++++++------------ test/staking/CoreStaking.test.ts | 22 ++------- test/staking/DPoStaking.test.ts | 21 +++++---- test/staking/Staking.test.ts | 2 +- test/validator/RoninValidatorSet.test.ts | 60 ++++++++++++++++++++++++ test/validator/ValidatorSetCore.test.ts | 2 +- 12 files changed, 119 insertions(+), 87 deletions(-) create mode 100644 test/validator/RoninValidatorSet.test.ts diff --git a/contracts/libraries/Math.sol b/contracts/libraries/Math.sol index b763abd67..92d6f2044 100644 --- a/contracts/libraries/Math.sol +++ b/contracts/libraries/Math.sol @@ -1,6 +1,6 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.9; +pragma solidity ^0.8.0; library Math { /** diff --git a/contracts/libraries/Sorting.sol b/contracts/libraries/Sorting.sol index 708ce03f0..c7e1ee050 100644 --- a/contracts/libraries/Sorting.sol +++ b/contracts/libraries/Sorting.sol @@ -1,6 +1,6 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.9; +pragma solidity ^0.8.0; library Sorting { struct Node { @@ -8,16 +8,16 @@ library Sorting { uint value; } - function sort(uint[] memory data) public pure returns (uint[] memory) { + function sort(uint[] memory data) internal pure returns (uint[] memory) { return _quickSort(data, int(0), int(data.length - 1)); } - function sortNodes(Node[] memory nodes) public pure returns (Node[] memory) { + function sortNodes(Node[] memory nodes) internal pure returns (Node[] memory) { // return _bubbleSortNodes(nodes); return _quickSortNodes(nodes, int(0), int(nodes.length - 1)); } - function sort(address[] memory _keys, uint256[] memory _values) public pure returns (address[] memory) { + function sort(address[] memory _keys, uint256[] memory _values) internal pure returns (address[] memory) { require(_keys.length > 0 && _values.length == _keys.length, "Sorting: invalid array length"); Node[] memory _nodes = new Node[](_keys.length); for (uint256 _i; _i < _nodes.length; _i++) { diff --git a/contracts/mocks/sorting/MockSorting.sol b/contracts/mocks/sorting/MockSorting.sol index c35810a86..8bed8ab22 100644 --- a/contracts/mocks/sorting/MockSorting.sol +++ b/contracts/mocks/sorting/MockSorting.sol @@ -24,16 +24,6 @@ contract MockSorting { return (data, data.length); } - function copyStorage2Memory() public view returns (uint256[] memory, uint256) { - uint256[] memory _tmpData = new uint256[](data.length); - - for (uint256 i; i < data.length; i++) { - _tmpData[i] = data[i]; - } - - return (_tmpData, data.length); - } - function sortAddressesAndValues(address[] calldata _addrs, uint256[] calldata _values) public pure diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index e5c4fb82c..af1ecd2d9 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -10,7 +10,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { mapping(address => mapping(address => uint256)) internal _delegatedAmount; modifier noEmptyValue() { - require(msg.value > 0, "StakingManager: cannot deposit empty value"); + require(msg.value > 0, "StakingManager: query with empty value"); _; } diff --git a/src/deploy/staking.ts b/src/deploy/staking.ts index 6a1a17f34..5d9677f4f 100644 --- a/src/deploy/staking.ts +++ b/src/deploy/staking.ts @@ -1,28 +1,19 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { Staking__factory } from '../types'; -import { StakingLibraryAddresses } from '../types/factories/Staking__factory'; -const deploy = async ({ getNamedAccounts, deployments}: HardhatRuntimeEnvironment) => { +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { const { deploy } = deployments; let { deployer } = await getNamedAccounts(); const proxyAdmin = await deployments.get('ProxyAdmin'); - const sortingLibrary = await deployments.get('SortingLibrary'); const stakingLogic = await deploy('StakingLogic', { contract: 'Staking', from: deployer, log: true, - libraries: { - Sorting: sortingLibrary.address, - }, }); - const param: StakingLibraryAddresses = { - ['contracts/libraries/Sorting.sol:Sorting']: sortingLibrary.address, - }; - - const data = new Staking__factory(param).interface.encodeFunctionData('initialize', []); + const data = new Staking__factory().interface.encodeFunctionData('initialize', []); await deploy('StakingProxy', { contract: 'TransparentUpgradeableProxy', diff --git a/test/sorting/MockSwapStorage.test.ts b/test/sorting/MockSwapStorage.test.ts index d97d6659d..fd009b3e8 100644 --- a/test/sorting/MockSwapStorage.test.ts +++ b/test/sorting/MockSwapStorage.test.ts @@ -29,7 +29,7 @@ const generateCandidate = ( }; }; -describe('Mock swap struct on storage tests', () => { +describe.skip('Mock swap struct on storage tests', () => { describe('Stress test', async () => { before(async () => { [admin, ...signers] = await ethers.getSigners(); diff --git a/test/sorting/QuickSort.test.ts b/test/sorting/QuickSort.test.ts index 723cb8c87..e53938c18 100644 --- a/test/sorting/QuickSort.test.ts +++ b/test/sorting/QuickSort.test.ts @@ -2,60 +2,60 @@ import { expect } from 'chai'; import { ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { Sorting, Sorting__factory, MockSorting, MockSorting__factory } from '../../src/types'; +import { MockSorting, MockSorting__factory } from '../../src/types'; -let quickSortLib: Sorting; let quickSortContract: MockSorting; let deployer: SignerWithAddress; let signers: SignerWithAddress[]; +const stats: { items: number; gasUsed: string }[] = []; + const runSortWithNRecords = async (numOfRecords: number) => { - let balances = []; + const balances = []; + const mapV = new Map(); for (let i = 0; i < numOfRecords; ++i) { + let value = 0; + do { + value = Math.floor(Math.random() * numOfRecords * 10_000); + } while (!!mapV.get(value)); + mapV.set(value, true); balances.push({ address: signers[i].address, - value: Math.floor(Math.random() * numOfRecords * 1000), + value, }); } balances.sort((a, b) => (a.value < b.value ? 1 : -1)); - let sorted = await quickSortContract.sortAddressesAndValues( + const gasUsed = await quickSortContract.estimateGas.sortAddressesAndValues( + balances.map((_) => _.address), + balances.map((_) => _.value) + ); + + const sorted = await quickSortContract.sortAddressesAndValues( balances.map((_) => _.address), balances.map((_) => _.value) ); expect(sorted).eql(balances.map((_) => _.address)); + return gasUsed.toString(); }; -describe('Quick sort', () => { +describe.skip('Quick sort test', () => { before(async () => { [deployer, ...signers] = await ethers.getSigners(); - quickSortLib = await new Sorting__factory(deployer).deploy(); - quickSortContract = await new MockSorting__factory( - { - 'contracts/libraries/Sorting.sol:Sorting': quickSortLib.address, - }, - deployer - ).deploy(); + quickSortContract = await new MockSorting__factory(deployer).deploy(); }); - describe('Sorting on sort(address[], uint[])', async () => { - it('Should sort correctly on 10 records', async () => { - await runSortWithNRecords(10); - }); - - it('Should sort correctly on 21 records', async () => { - await runSortWithNRecords(21); - }); - - it('Should sort correctly on 50 records', async () => { - await runSortWithNRecords(50); - }); - - it('Should sort correctly on 99 records', async () => { - await runSortWithNRecords(99); - }); + after(() => { + console.table(stats); }); + + for (let i = 1; i < 100; i++) { + i % 10 == 9 && + it(`Should sort correctly on ${i} records`, async () => { + stats.push({ items: i, gasUsed: await runSortWithNRecords(i) }); + }); + } }); diff --git a/test/staking/CoreStaking.test.ts b/test/staking/CoreStaking.test.ts index 2e90c684a..6a4d20fe7 100644 --- a/test/staking/CoreStaking.test.ts +++ b/test/staking/CoreStaking.test.ts @@ -88,6 +88,10 @@ describe('Core Staking test', () => { local.reset(); }); + after(async () => { + await network.provider.send('evm_setAutomine', [true]); + }); + it('Should work properly with staking actions occurring sequentially for a normal period', async () => { await stakingContract.stake(userA.address, 100); await stakingContract.stake(userB.address, 100); @@ -323,18 +327,7 @@ describe('Core Staking test', () => { await stakingContract.stake(userA.address, 100); await stakingContract.unstake(userA.address, 100); await stakingContract.stake(userA.address, 100); - await stakingContract.unstake(userA.address, 100); - await stakingContract.stake(userB.address, 200); - await stakingContract.unstake(userB.address, 200); - await stakingContract.stake(userB.address, 200); - await stakingContract.unstake(userB.address, 200); - await stakingContract.stake(userB.address, 200); - await stakingContract.stake(userA.address, 100); - await stakingContract.unstake(userA.address, 100); - await stakingContract.unstake(userB.address, 200); await stakingContract.stake(userB.address, 200); - await stakingContract.unstake(userA.address, 100); - await stakingContract.stake(userA.address, 100); await stakingContract.unstake(userB.address, 200); await stakingContract.recordRewardForDelegators(1000); await stakingContract.commitRewardPool(); @@ -375,13 +368,6 @@ describe('Core Staking test', () => { local.commitRewardPool(); await expectLocalCalculationRight(); - await stakingContract.claimReward(userA.address); - await stakingContract.claimReward(userB.address); - await stakingContract.claimReward(userA.address); - await stakingContract.claimReward(userB.address); - await stakingContract.claimReward(userA.address); - await stakingContract.claimReward(userB.address); - await stakingContract.claimReward(userA.address); await stakingContract.claimReward(userA.address); await stakingContract.claimReward(userA.address); await stakingContract.claimReward(userB.address); diff --git a/test/staking/DPoStaking.test.ts b/test/staking/DPoStaking.test.ts index c17c4da7a..d3f91ce4f 100644 --- a/test/staking/DPoStaking.test.ts +++ b/test/staking/DPoStaking.test.ts @@ -94,9 +94,11 @@ describe('DPoStaking test', () => { [deployer, proxyAdmin, userA, userB, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); validatorCandidates = validatorCandidates.slice(0, 3); const nonce = await deployer.getTransactionCount(); - const proxyContractAddress = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); - validatorContract = await new MockValidatorSetForStaking__factory(deployer).deploy(proxyContractAddress, 10, 2); + const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); + validatorContract = await new MockValidatorSetForStaking__factory(deployer).deploy(stakingContractAddr, 10, 2); + await validatorContract.deployed(); const logicContract = await new DPoStaking__factory(deployer).deploy(); + await logicContract.deployed(); const proxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( logicContract.address, proxyAdmin.address, @@ -107,8 +109,9 @@ describe('DPoStaking test', () => { minValidatorBalance, ]) ); + await proxyContract.deployed(); stakingContract = DPoStaking__factory.connect(proxyContract.address, deployer); - expect(proxyContractAddress.toLowerCase()).eq(proxyContract.address.toLowerCase()); + expect(stakingContractAddr.toLowerCase()).eq(proxyContract.address.toLowerCase()); }); describe('Validator candidate test', () => { @@ -131,12 +134,12 @@ describe('DPoStaking test', () => { poolAddr = validatorCandidates[1]; otherPoolAddr = validatorCandidates[2]; - expect(await stakingContract.callStatic.totalBalance(poolAddr.address)).eq(minValidatorBalance); + expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance); }); it('Should not be able to stake with empty value', async () => { await expect(stakingContract.stake(poolAddr.address, { value: 0 })).revertedWith( - 'StakingManager: query for empty value' + 'StakingManager: query with empty value' ); }); @@ -152,6 +155,8 @@ describe('DPoStaking test', () => { it('Should be able to stake/unstake as a validator', async () => { await stakingContract.connect(poolAddr).stake(poolAddr.address, { value: 1 }); expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance.add(1)); + await stakingContract.connect(poolAddr).unstake(poolAddr.address, 1); + expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance); }); it('Should be not able to unstake with the balance left is not larger than the minimum balance threshold', async () => { @@ -164,16 +169,16 @@ describe('DPoStaking test', () => { describe('Delegator test', () => { it('Should not be able to delegate with empty value', async () => { await expect(stakingContract.delegate(otherPoolAddr.address)).revertedWith( - 'StakingManager: query for empty value' + 'StakingManager: query with empty value' ); }); it('Should not be able to delegate/undelegate when the method caller is the candidate owner', async () => { await expect(stakingContract.connect(poolAddr).delegate(poolAddr.address, { value: 1 })).revertedWith( - 'StakingManager: method caller is the candidate admin' + 'StakingManager: method caller must not be the candidate admin' ); await expect(stakingContract.connect(poolAddr).undelegate(poolAddr.address, 1)).revertedWith( - 'StakingManager: method caller is the candidate admin' + 'StakingManager: method caller must not be the candidate admin' ); }); diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index c2354463d..b764b3701 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -57,7 +57,7 @@ const validateTwoObjects = async (definedObj: any, resultObj: any) => { } }; -describe('Staking test', () => { +describe.skip('Staking test', () => { let unstakingOnHoldBlocksNum: BigNumber; let minValidatorBalance: BigNumber; diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts new file mode 100644 index 000000000..b6c454c28 --- /dev/null +++ b/test/validator/RoninValidatorSet.test.ts @@ -0,0 +1,60 @@ +// import { expect } from 'chai'; +// import { ethers } from 'hardhat'; +// import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; + +// import { MockSorting, MockSorting__factory } from '../../src/types'; + +// let quickSortContract: MockSorting; + +// let deployer: SignerWithAddress; +// let signers: SignerWithAddress[]; + +// const stats: { items: number; gasUsed: string; }[] = []; + +// const runSortWithNRecords = async (numOfRecords: number) => { +// const balances = []; +// const mapV = new Map(); +// for (let i = 0; i < numOfRecords; ++i) { +// let value = 0; +// do { +// value = Math.floor(Math.random() * numOfRecords * 10_000); +// } while (!!mapV.get(value)); +// mapV.set(value, true); +// balances.push({ +// address: signers[i].address, +// value, +// }); +// } + +// balances.sort((a, b) => (a.value < b.value ? 1 : -1)); + +// const gasUsed = await quickSortContract.estimateGas.sortAddressesAndValues( +// balances.map((_) => _.address), +// balances.map((_) => _.value) +// ); + +// const sorted = await quickSortContract.sortAddressesAndValues( +// balances.map((_) => _.address), +// balances.map((_) => _.value) +// ); + +// expect(sorted).eql(balances.map((_) => _.address)); +// return gasUsed.toString(); +// }; + +// describe('Quick sort test', () => { +// before(async () => { +// [deployer, ...signers] = await ethers.getSigners(); +// quickSortContract = await new MockSorting__factory(deployer).deploy(); +// }); + +// after(() => { +// console.table(stats); +// }); + +// for (let i = 1; i < 100; i++) { +// i % 10 == 9 && it(`Should sort correctly on ${i} records`, async () => { +// stats.push({ items: i, gasUsed: await runSortWithNRecords(i) }); +// }); +// } +// }); diff --git a/test/validator/ValidatorSetCore.test.ts b/test/validator/ValidatorSetCore.test.ts index 1c157afb1..580c36f39 100644 --- a/test/validator/ValidatorSetCore.test.ts +++ b/test/validator/ValidatorSetCore.test.ts @@ -32,7 +32,7 @@ const generateCandidate = ( }; }; -describe('Validator set core tests', () => { +describe.skip('Validator set core tests', () => { describe('CRUD functions on validator set', async () => { describe('Simple CRUD', async () => { before(async () => { From 378170489e0cae3df8d4883e405fd21acbe812dd Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 6 Sep 2022 15:32:50 +0700 Subject: [PATCH 069/190] [RoninValidatorSet] Rename fn & apply proxy pattern --- contracts/interfaces/IRoninValidatorSet.sol | 8 +-- .../ronin-validator/RoninValidatorSet.sol | 60 ++++++++++++++----- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index 3a160d436..aa854bad0 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -117,12 +117,12 @@ interface IRoninValidatorSet { function getValidators() external view returns (address[] memory); /** - * @dev Returns whether the epoch ended at the block number `_block`. + * @dev Returns whether the epoch ending is at the block number `_block`. */ - function epochEnded(uint256 _block) external view returns (bool); + function epochEndingAt(uint256 _block) external view returns (bool); /** - * @dev Returns whether the period ended at the block number `_block`. + * @dev Returns whether the period ending is at the block number `_block`. */ - function periodEnded(uint256 _block) external view returns (bool); + function periodEndingAt(uint256 _block) external view returns (bool); } diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index 9879b584a..b080bee86 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.9; +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "../interfaces/ISlashIndicator.sol"; import "../interfaces/IStaking.sol"; @@ -9,7 +10,7 @@ import "../interfaces/IRoninValidatorSet.sol"; import "../libraries/Sorting.sol"; import "../libraries/Math.sol"; -contract RoninValidatorSet is IRoninValidatorSet { +contract RoninValidatorSet is IRoninValidatorSet, Initializable { /// @dev Governance admin contract address. address internal _governanceAdminContract; // TODO(Thor): add setter. /// @dev Slash indicator contract address. @@ -37,6 +38,7 @@ contract RoninValidatorSet is IRoninValidatorSet { mapping(uint256 => mapping(address => bool)) internal _noPendingReward; /// @dev Mapping from validator address => the last block that the validator is jailed mapping(address => uint256) internal _jailedUntil; + /// @dev Mapping from validator address => pending reward from producing block mapping(address => uint256) internal _miningReward; /// @dev Mapping from validator address => pending reward from delegating @@ -48,16 +50,46 @@ contract RoninValidatorSet is IRoninValidatorSet { uint256 public slashDoubleSignAmount; modifier onlyCoinbase() { - require(msg.sender == block.coinbase, "RoninValidatorSet: method caller is not coinbase"); + require(msg.sender == block.coinbase, "RoninValidatorSet: method caller must be coinbase"); _; } - modifier whenEndEpoch() { - require(epochEnded(block.number), "RoninValidatorSet: only allowed at the end of epoch"); + modifier onlySlashIndicatorContract() { + require(msg.sender == _slashIndicatorContract, "RoninValidatorSet: method caller must be slash indicator contract"); _; } - constructor() {} + modifier whenEpochEnding() { + require(epochEndingAt(block.number), "RoninValidatorSet: only allowed at the end of epoch"); + _; + } + + constructor() { + _disableInitializers(); + } + + /** + * @dev Initializes the contract storage. + */ + function initialize( + address __governanceAdminContract, + address __slashIndicatorContract, + address __stakingContract, + uint256 __maxValidatorNumber, + uint256 __numberOfBlocksInEpoch, + uint256 __numberOfEpochsInPeriod, + uint256 _slashFelonyAmount, + uint256 _slashDoubleSignAmount + ) external initializer { + _governanceAdminContract = __governanceAdminContract; + _slashIndicatorContract = __slashIndicatorContract; + _stakingContract = __stakingContract; + _maxValidatorNumber = __maxValidatorNumber; + _numberOfBlocksInEpoch = __numberOfBlocksInEpoch; + _numberOfEpochsInPeriod = __numberOfEpochsInPeriod; + slashFelonyAmount = _slashFelonyAmount; + slashDoubleSignAmount = _slashDoubleSignAmount; + } /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR COINBASE // @@ -93,7 +125,7 @@ contract RoninValidatorSet is IRoninValidatorSet { /** * @inheritdoc IRoninValidatorSet */ - function wrapUpEpoch() external payable override onlyCoinbase whenEndEpoch { + function wrapUpEpoch() external payable override onlyCoinbase whenEpochEnding { IStaking _staking = IStaking(_stakingContract); ISlashIndicator _slashIndicator = ISlashIndicator(_slashIndicatorContract); @@ -103,7 +135,7 @@ contract RoninValidatorSet is IRoninValidatorSet { _slashIndicator.resetCounter(_validatorAddr); if (!_jailed(_validatorAddr) && !_noPendingReward[periodOf(block.number)][_validatorAddr]) { - if (periodEnded(block.number)) { + if (periodEndingAt(block.number)) { uint256 _miningAmount = _miningReward[_validatorAddr]; _miningReward[_validatorAddr] = 0; if (_miningAmount > 0) { @@ -166,7 +198,7 @@ contract RoninValidatorSet is IRoninValidatorSet { /** * @inheritdoc IRoninValidatorSet */ - function slashMisdemeanor(address _validatorAddr) public override { + function slashMisdemeanor(address _validatorAddr) public override onlySlashIndicatorContract { _noPendingReward[periodOf(block.number)][_validatorAddr] = true; _miningReward[_validatorAddr] = 0; _delegatingReward[_validatorAddr] = 0; @@ -176,7 +208,7 @@ contract RoninValidatorSet is IRoninValidatorSet { /** * @inheritdoc IRoninValidatorSet */ - function slashFelony(address _validatorAddr) external override { + function slashFelony(address _validatorAddr) external override onlySlashIndicatorContract { slashMisdemeanor(_validatorAddr); IStaking(_stakingContract).deductStakingAmount(_validatorAddr, slashFelonyAmount); uint256 _jailedBlock = block.number + 2 * 28800; // TODO: make this constant number to variable @@ -187,7 +219,7 @@ contract RoninValidatorSet is IRoninValidatorSet { /** * @inheritdoc IRoninValidatorSet */ - function slashDoubleSign(address _validatorAddr) external override { + function slashDoubleSign(address _validatorAddr) external override onlySlashIndicatorContract { slashMisdemeanor(_validatorAddr); IStaking(_stakingContract).deductStakingAmount(_validatorAddr, slashDoubleSignAmount); _jailedUntil[_validatorAddr] = type(uint256).max; @@ -224,14 +256,14 @@ contract RoninValidatorSet is IRoninValidatorSet { /** * @inheritdoc IRoninValidatorSet */ - function epochOf(uint256 _block) public view override returns (uint256) { + function epochOf(uint256 _block) public view virtual override returns (uint256) { return _block / _numberOfBlocksInEpoch + 1; } /** * @inheritdoc IRoninValidatorSet */ - function periodOf(uint256 _block) public view override returns (uint256) { + function periodOf(uint256 _block) public view virtual override returns (uint256) { return _block / (_numberOfBlocksInEpoch * _numberOfEpochsInPeriod) + 1; } @@ -248,14 +280,14 @@ contract RoninValidatorSet is IRoninValidatorSet { /** * @inheritdoc IRoninValidatorSet */ - function epochEnded(uint256 _block) public view returns (bool) { + function epochEndingAt(uint256 _block) public view virtual returns (bool) { return _block % _numberOfBlocksInEpoch == _numberOfBlocksInEpoch - 1; } /** * @inheritdoc IRoninValidatorSet */ - function periodEnded(uint256 _block) public view returns (bool) { + function periodEndingAt(uint256 _block) public view virtual returns (bool) { uint256 _blockLength = _numberOfBlocksInEpoch * _numberOfEpochsInPeriod; return _block % _blockLength == _blockLength - 1; } From f64ebc38aa9244e8917dfc6e542232aa52c98041 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 6 Sep 2022 15:33:33 +0700 Subject: [PATCH 070/190] Remove unused deploy & add dpostaking deployment script --- src/deploy/dpostaking.ts | 32 ++++++++++++++++++++++++++++++++ src/deploy/libraries/sorting.ts | 16 ---------------- 2 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 src/deploy/dpostaking.ts delete mode 100644 src/deploy/libraries/sorting.ts diff --git a/src/deploy/dpostaking.ts b/src/deploy/dpostaking.ts new file mode 100644 index 000000000..e7b2fea2f --- /dev/null +++ b/src/deploy/dpostaking.ts @@ -0,0 +1,32 @@ +import { network } from 'hardhat'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { stakingConfig } from '../script/dpostaking'; +import { DPoStaking__factory } from '../types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + const proxyAdmin = await deployments.get('ProxyAdmin'); + const logicContract = await deployments.get('DPoStakingLogic'); + + const data = new DPoStaking__factory().interface.encodeFunctionData('initialize', [ + stakingConfig[network.name]!.unstakingOnHoldBlocksNum, + stakingConfig[network.name]!.validatorContract, + stakingConfig[network.name]!.governanceAdminContract, + stakingConfig[network.name]!.maxValidatorCandidate, + stakingConfig[network.name]!.minValidatorBalance, + ]); + + await deploy('DPoStakingProxy', { + contract: 'TransparentUpgradeableProxy', + from: deployer, + log: true, + args: [logicContract.address, proxyAdmin.address, data], + }); +}; + +deploy.tags = ['DPoStakingProxy']; +deploy.dependencies = ['ProxyAdmin', 'DPoStakingLogic']; + +export default deploy; diff --git a/src/deploy/libraries/sorting.ts b/src/deploy/libraries/sorting.ts deleted file mode 100644 index e01f6f602..000000000 --- a/src/deploy/libraries/sorting.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { HardhatRuntimeEnvironment } from 'hardhat/types'; - -const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { - const { deploy } = deployments; - const { deployer } = await getNamedAccounts(); - - await deploy('SortingLibrary', { - contract: 'Sorting', - from: deployer, - log: true, - }); -}; - -deploy.tags = ['SortingLibrary']; - -export default deploy; From 116e5970424ae626e34fc1a882903cd3375c3ae7 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 6 Sep 2022 15:33:56 +0700 Subject: [PATCH 071/190] Remove unused deploy --- src/deploy-tmp/dpostaking.ts | 32 ------------------------------ src/deploy-tmp/logic/dpostaking.ts | 17 ---------------- src/deploy-tmp/proxy-admin.ts | 15 -------------- 3 files changed, 64 deletions(-) delete mode 100644 src/deploy-tmp/dpostaking.ts delete mode 100644 src/deploy-tmp/logic/dpostaking.ts delete mode 100644 src/deploy-tmp/proxy-admin.ts diff --git a/src/deploy-tmp/dpostaking.ts b/src/deploy-tmp/dpostaking.ts deleted file mode 100644 index e7b2fea2f..000000000 --- a/src/deploy-tmp/dpostaking.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { network } from 'hardhat'; -import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { stakingConfig } from '../script/dpostaking'; -import { DPoStaking__factory } from '../types'; - -const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { - const { deploy } = deployments; - const { deployer } = await getNamedAccounts(); - - const proxyAdmin = await deployments.get('ProxyAdmin'); - const logicContract = await deployments.get('DPoStakingLogic'); - - const data = new DPoStaking__factory().interface.encodeFunctionData('initialize', [ - stakingConfig[network.name]!.unstakingOnHoldBlocksNum, - stakingConfig[network.name]!.validatorContract, - stakingConfig[network.name]!.governanceAdminContract, - stakingConfig[network.name]!.maxValidatorCandidate, - stakingConfig[network.name]!.minValidatorBalance, - ]); - - await deploy('DPoStakingProxy', { - contract: 'TransparentUpgradeableProxy', - from: deployer, - log: true, - args: [logicContract.address, proxyAdmin.address, data], - }); -}; - -deploy.tags = ['DPoStakingProxy']; -deploy.dependencies = ['ProxyAdmin', 'DPoStakingLogic']; - -export default deploy; diff --git a/src/deploy-tmp/logic/dpostaking.ts b/src/deploy-tmp/logic/dpostaking.ts deleted file mode 100644 index 945bc9efa..000000000 --- a/src/deploy-tmp/logic/dpostaking.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { HardhatRuntimeEnvironment } from 'hardhat/types'; - -const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { - const { deploy } = deployments; - const { deployer } = await getNamedAccounts(); - - await deploy('DPoStakingLogic', { - contract: 'DPoStaking', - from: deployer, - log: true, - }); -}; - -deploy.tags = ['DPoStakingLogic']; -deploy.dependencies = ['ProxyAdmin']; - -export default deploy; diff --git a/src/deploy-tmp/proxy-admin.ts b/src/deploy-tmp/proxy-admin.ts deleted file mode 100644 index 781cf2f9e..000000000 --- a/src/deploy-tmp/proxy-admin.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { HardhatRuntimeEnvironment } from 'hardhat/types'; - -const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { - const { deploy } = deployments; - const { deployer } = await getNamedAccounts(); - - await deploy('ProxyAdmin', { - from: deployer, - log: true, - }); -}; - -deploy.tags = ['ProxyAdmin']; - -export default deploy; From 425974846ddd42a1ab723d27b3cf831e3c202e18 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 6 Sep 2022 17:41:26 +0700 Subject: [PATCH 072/190] [SlashIndicator] update contract & its interface --- contracts/interfaces/ISlashIndicator.sol | 24 ++----------- contracts/slash/SlashIndicator.sol | 43 +++++++++--------------- 2 files changed, 19 insertions(+), 48 deletions(-) diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index 03b2532ce..b5e2ff8f4 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -7,7 +7,7 @@ import "../interfaces/IRoninValidatorSet.sol"; interface ISlashIndicator { // TODO: fill comment for event. IE: Emitted when... event ValidatorSlashed(address indexed validator, SlashType slashType); - event UnavailabilityIndicatorReset(address indexed validator); + event UnavailabilityIndicatorsReset(address[] validators); enum SlashType { UNKNOWN, @@ -16,13 +16,6 @@ interface ISlashIndicator { DOUBLE_SIGNING } - struct Indicator { - /// @dev The block height that the indicator get updated, make sure this update once each block - uint256 lastSyncedBlock; - /// @dev Number of missed block the validator, reset everyday or once reaching the fenoly threshold - uint128 counter; - } - /** * @dev Returns the validator contract. */ @@ -40,24 +33,13 @@ interface ISlashIndicator { */ function slash(address _valAddr) external; - /** - * @dev Resets the counter of the validator at the end of every period - * - * Requirements: - * - Only validator contract can call this method - * - * Emits the event `UnavailabilityIndicatorReset`. - * - */ - function resetCounter(address) external; - /** * @dev Resets the counter of all validators at the end of every period * * Requirements: * - Only validator contract can call this method * - * Emits the `UnavailabilityIndicatorReset` events. + * Emits the `UnavailabilityIndicatorsReset` events. * */ function resetCounters(address[] calldata) external; @@ -74,7 +56,7 @@ interface ISlashIndicator { /** * @dev Gets slash indicator of a validator. */ - function getSlashIndicator(address _validator) external view returns (Indicator memory); + function getSlashIndicator(address _validator) external view returns (uint256); /** * @dev Gets slash threshold. diff --git a/contracts/slash/SlashIndicator.sol b/contracts/slash/SlashIndicator.sol index a24c779e6..b8fa40174 100644 --- a/contracts/slash/SlashIndicator.sol +++ b/contracts/slash/SlashIndicator.sol @@ -11,7 +11,7 @@ import "../interfaces/IRoninValidatorSet.sol"; */ contract SlashIndicator is ISlashIndicator { /// @dev Mapping from validator address => unavailability indicator - mapping(address => Indicator) internal _unavailabilityIndicator; + mapping(address => uint256) internal _unavailabilityIndicator; /// @dev The last block that a validator is slashed uint256 public lastSlashedBlock; @@ -48,15 +48,13 @@ contract SlashIndicator is ISlashIndicator { * @inheritdoc ISlashIndicator */ function slash(address _validatorAddr) external override onlyCoinbase oncePerBlock { - Indicator storage indicator = _unavailabilityIndicator[_validatorAddr]; - indicator.counter++; - indicator.lastSyncedBlock = block.number; + uint256 _count = ++_unavailabilityIndicator[_validatorAddr]; // Slashs the validator as either the fenoly or the misdemeanor - if (indicator.counter == felonyThreshold) { + if (_count == felonyThreshold) { validatorContract.slashFelony(_validatorAddr); emit ValidatorSlashed(_validatorAddr, SlashType.FELONY); - } else if (indicator.counter == misdemeanorThreshold) { + } else if (_count == misdemeanorThreshold) { validatorContract.slashMisdemeanor(_validatorAddr); emit ValidatorSlashed(_validatorAddr, SlashType.MISDEMAENOR); } @@ -66,21 +64,7 @@ contract SlashIndicator is ISlashIndicator { * @inheritdoc ISlashIndicator */ function resetCounters(address[] calldata _validatorAddrs) external override onlyValidatorContract { - if (_validatorAddrs.length == 0) { - return; - } - - for (uint256 _i; _i < _validatorAddrs.length; _i++) { - _resetCounter(_validatorAddrs[_i]); - } - } - - /** - * @inheritdoc ISlashIndicator - */ - function resetCounter(address _validatorAddr) external override onlyValidatorContract { - _resetCounter(_validatorAddr); - emit UnavailabilityIndicatorReset(_validatorAddr); + _resetCounters(_validatorAddrs); } /** @@ -93,8 +77,8 @@ contract SlashIndicator is ISlashIndicator { /** * @inheritdoc ISlashIndicator */ - function getSlashIndicator(address validator) external view override returns (Indicator memory _indicator) { - _indicator = _unavailabilityIndicator[validator]; + function getSlashIndicator(address validator) external view override returns (uint256) { + return _unavailabilityIndicator[validator]; } /** @@ -107,9 +91,14 @@ contract SlashIndicator is ISlashIndicator { /** * @dev Resets counter for the validator address. */ - function _resetCounter(address _validatorAddr) private { - Indicator storage _indicator = _unavailabilityIndicator[_validatorAddr]; - _indicator.counter = 0; - _indicator.lastSyncedBlock = block.number; + function _resetCounters(address[] calldata _validatorAddrs) private { + if (_validatorAddrs.length == 0) { + return; + } + + for (uint _i = 0; _i < _validatorAddrs.length; _i++) { + delete _unavailabilityIndicator[_validatorAddrs[_i]]; + } + emit UnavailabilityIndicatorsReset(_validatorAddrs); } } From 04d50d5171120487f5b8a9f322ba1daba7e73fde Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 6 Sep 2022 17:42:57 +0700 Subject: [PATCH 073/190] Rename variable & lint fix --- contracts/interfaces/IStaking.sol | 15 +++- contracts/libraries/Sorting.sol | 6 +- contracts/mocks/MockValidatorSetCore.sol | 11 ++- contracts/mocks/staking/MockStaking.sol | 6 +- .../staking/MockValidatorSetForStaking.sol | 8 +- contracts/mocks/validator/MockValidator.sol | 68 ++++++++++++++++ .../ronin-validator/RoninValidatorSet.sol | 77 ++++++++++++------- contracts/staking/DPoStaking.sol | 15 +++- contracts/staking/RewardCalculation.sol | 2 +- contracts/staking/Staking.sol | 6 +- contracts/validator/ValidatorSet.sol | 2 +- hardhat.config.ts | 2 +- src/addresses.ts | 2 +- src/deploy/dpostaking.ts | 1 - src/deploy/logic/dpostaking.ts | 17 ++++ src/script/dpostaking.ts | 1 - 16 files changed, 188 insertions(+), 51 deletions(-) create mode 100644 contracts/mocks/validator/MockValidator.sol create mode 100644 src/deploy/logic/dpostaking.ts diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 9d05f0b10..869c8ff61 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -117,7 +117,7 @@ interface IStaking { * @notice This method should not be called after the pending pool is sinked. * */ - function recordRewardForDelegators(address _consensusAddr, uint256 _reward) external payable; + function recordReward(address _consensusAddr, uint256 _reward) external payable; /** * @dev Settles the pending pool and allocates rewards for the pool `_consensusAddr`. @@ -128,7 +128,18 @@ interface IStaking { * Emits the `SettledPoolUpdated` event. * */ - function settleRewardPoolForDelegators(address _consensusAddr) external; + function settleRewardPool(address _consensusAddr) external; + + /** + * @dev Settles the pending pool and allocates rewards for the pool `_consensusAddr`. + * + * Requirements: + * - The method caller is validator contract. + * + * Emits the `SettledPoolUpdated` event. + * + */ + function settleMultipleRewardPools(address[] calldata _consensusAddrs) external; /** * @dev Handles when the pending reward pool of the validator is sinked. diff --git a/contracts/libraries/Sorting.sol b/contracts/libraries/Sorting.sol index c7e1ee050..e9405822f 100644 --- a/contracts/libraries/Sorting.sol +++ b/contracts/libraries/Sorting.sol @@ -18,7 +18,11 @@ library Sorting { } function sort(address[] memory _keys, uint256[] memory _values) internal pure returns (address[] memory) { - require(_keys.length > 0 && _values.length == _keys.length, "Sorting: invalid array length"); + require(_values.length == _keys.length, "Sorting: invalid array length"); + if (_keys.length == 0) { + return _keys; + } + Node[] memory _nodes = new Node[](_keys.length); for (uint256 _i; _i < _nodes.length; _i++) { _nodes[_i] = Node(uint256(uint160(_keys[_i])), _values[_i]); diff --git a/contracts/mocks/MockValidatorSetCore.sol b/contracts/mocks/MockValidatorSetCore.sol index f90c20088..7087c5e28 100644 --- a/contracts/mocks/MockValidatorSetCore.sol +++ b/contracts/mocks/MockValidatorSetCore.sol @@ -10,7 +10,6 @@ import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; * @notice This contract maintains set of validator in the current epoch of Ronin network */ contract MockValidatorSetCore is ValidatorSetCore { - function isInCurrentValidatorSet(address _addr) external view returns (bool) { return _isInCurrentValidatorSet(_addr); } @@ -44,7 +43,15 @@ contract MockValidatorSetCore is ValidatorSetCore { return _getValidator(_valAddr); } - function tryGetValidator(address _valAddr) external view returns (bool, IValidatorSet.Validator memory, uint256) { + function tryGetValidator(address _valAddr) + external + view + returns ( + bool, + IValidatorSet.Validator memory, + uint256 + ) + { return _tryGetValidator(_valAddr); } diff --git a/contracts/mocks/staking/MockStaking.sol b/contracts/mocks/staking/MockStaking.sol index 5152a2c9b..73c1ef7fa 100644 --- a/contracts/mocks/staking/MockStaking.sol +++ b/contracts/mocks/staking/MockStaking.sol @@ -45,8 +45,8 @@ contract MockStaking is RewardCalculation { _sinkPendingReward(poolAddr); } - function recordRewardForDelegators(uint256 _rewardAmount) external { - _recordRewardForDelegators(poolAddr, _rewardAmount); + function recordReward(uint256 _rewardAmount) external { + _recordReward(poolAddr, _rewardAmount); } function commitRewardPool() external { @@ -54,7 +54,7 @@ contract MockStaking is RewardCalculation { } function increaseAccumulatedRps(uint256 _amount) external { - _recordRewardForDelegators(poolAddr, _amount); + _recordReward(poolAddr, _amount); } function getPeriod() public view returns (uint256) { diff --git a/contracts/mocks/staking/MockValidatorSetForStaking.sol b/contracts/mocks/staking/MockValidatorSetForStaking.sol index 420f08182..f5f6dbac6 100644 --- a/contracts/mocks/staking/MockValidatorSetForStaking.sol +++ b/contracts/mocks/staking/MockValidatorSetForStaking.sol @@ -25,13 +25,11 @@ contract MockValidatorSetForStaking is IValidatorSet { } function depositReward() external payable override { - stakingContract.recordRewardForDelegators{ value: msg.value }(msg.sender, msg.value); + stakingContract.recordReward{ value: msg.value }(msg.sender, msg.value); } - function settledReward(address[] memory _validatorList) external { - for (uint _i = 0; _i < _validatorList.length; _i++) { - stakingContract.settleRewardPoolForDelegators(_validatorList[_i]); - } + function settledReward(address[] calldata _validatorList) external { + stakingContract.settleMultipleRewardPools(_validatorList); } function slashMisdemeanor(address _validator) external override { diff --git a/contracts/mocks/validator/MockValidator.sol b/contracts/mocks/validator/MockValidator.sol new file mode 100644 index 000000000..4734cdf7c --- /dev/null +++ b/contracts/mocks/validator/MockValidator.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../../ronin-validator/RoninValidatorSet.sol"; + +contract MockRoninValidatorSet is RoninValidatorSet { + uint256[] internal _epochs; + uint256[] internal _periods; + + constructor( + address __governanceAdminContract, + address __slashIndicatorContract, + address __stakingContract, + uint256 __maxValidatorNumber, + uint256 _slashFelonyAmount, + uint256 _slashDoubleSignAmount + ) { + _governanceAdminContract = __governanceAdminContract; + _slashIndicatorContract = __slashIndicatorContract; + _stakingContract = __stakingContract; + _maxValidatorNumber = __maxValidatorNumber; + slashFelonyAmount = _slashFelonyAmount; + slashDoubleSignAmount = _slashDoubleSignAmount; + } + + function endEpoch() external { + _epochs.push(block.number); + } + + function endPeriod() external { + _periods.push(block.number); + } + + function periodOf(uint256 _block) public view override returns (uint256 _period) { + for (uint256 _i; _i < _periods.length; _i++) { + if (_block >= _periods[_i]) { + _period = _i + 1; + } + } + } + + function epochOf(uint256 _block) public view override returns (uint256 _epoch) { + for (uint256 _i; _i < _periods.length; _i++) { + if (_block >= _epochs[_i]) { + _epoch = _i + 1; + } + } + } + + function epochEndingAt(uint256 _block) public view override returns (bool) { + for (uint _i = 0; _i < _epochs.length; _i++) { + if (_block == _epochs[_i]) { + return true; + } + } + return false; + } + + function periodEndingAt(uint256 _block) public view override returns (bool) { + for (uint _i = 0; _i < _periods.length; _i++) { + if (_block == _periods[_i]) { + return true; + } + } + return false; + } +} diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index b080bee86..ed101c40f 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.9; +import "hardhat/console.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "../interfaces/ISlashIndicator.sol"; @@ -118,7 +119,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _miningReward[_validatorAddr] += _miningAmount; _delegatingReward[_validatorAddr] += _delegatingAmount; - IStaking(_stakingContract).recordRewardForDelegators(_validatorAddr, _delegatingAmount); + IStaking(_stakingContract).recordReward(_validatorAddr, _delegatingAmount); // TODO(Thor): emit event } @@ -126,41 +127,57 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { * @inheritdoc IRoninValidatorSet */ function wrapUpEpoch() external payable override onlyCoinbase whenEpochEnding { + console.log("0", gasleft()); IStaking _staking = IStaking(_stakingContract); - ISlashIndicator _slashIndicator = ISlashIndicator(_slashIndicatorContract); address _validatorAddr; - for (uint _i = 0; _i < validatorCount; _i++) { - _validatorAddr = _validator[_i]; - _slashIndicator.resetCounter(_validatorAddr); - - if (!_jailed(_validatorAddr) && !_noPendingReward[periodOf(block.number)][_validatorAddr]) { - if (periodEndingAt(block.number)) { - uint256 _miningAmount = _miningReward[_validatorAddr]; - _miningReward[_validatorAddr] = 0; - if (_miningAmount > 0) { - // TODO(Thor): use `call` to transfer reward with reentrancy gruard - require( - payable(_staking.treasuryAddressOf(_validatorAddr)).send(_miningAmount), - "RoninValidatorSet: could not transfer RON" - ); - } - } + uint256 _delegatingAmount; + uint256 _period = periodOf(block.number); + bool _periodEnding = periodEndingAt(block.number); + + console.log("1", gasleft()); + address[] memory _validators = getValidators(); + for (uint _i = 0; _i < _validators.length; _i++) { + _validatorAddr = _validators[_i]; + + if (_jailed(_validatorAddr) || _noPendingReward[_period][_validatorAddr]) { + continue; + } - uint256 _delegatingAmount = _delegatingReward[_validatorAddr]; - _delegatingReward[_validatorAddr] = 0; - if (_delegatingAmount > 0) { + if (_periodEnding) { + uint256 _miningAmount = _miningReward[_validatorAddr]; + _miningReward[_validatorAddr] = 0; + if (_miningAmount > 0) { // TODO(Thor): use `call` to transfer reward with reentrancy gruard - require(payable(address(_staking)).send(_delegatingAmount), "RoninValidatorSet: could not transfer RON"); + require( + payable(_staking.treasuryAddressOf(_validatorAddr)).send(_miningAmount), + "RoninValidatorSet: could not transfer RON" + ); } - _staking.settleRewardPoolForDelegators(_validatorAddr); - _miningReward[_validatorAddr] = 0; - _delegatingReward[_validatorAddr] = 0; } - // TODO: emit event + + _delegatingAmount += _delegatingReward[_validatorAddr]; + _delegatingReward[_validatorAddr] = 0; + // TODO: emit events + } + + console.log("2", gasleft()); + // if (_periodEnding) {} + + ISlashIndicator(_slashIndicatorContract).resetCounters(_validators); + console.log("2.a", gasleft()); + _staking.settleMultipleRewardPools(_validators); + + console.log("3", gasleft()); + + if (_delegatingAmount > 0) { + // TODO(Thor): use `call` to transfer reward with reentrancy gruard + require(payable(address(_staking)).send(_delegatingAmount), "RoninValidatorSet: could not transfer RON"); } + console.log("4", gasleft()); _updateValidatorSet(); + console.log("5", gasleft()); } /** @@ -270,7 +287,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { /** * @inheritdoc IRoninValidatorSet */ - function getValidators() external view override returns (address[] memory _validatorList) { + function getValidators() public view override returns (address[] memory _validatorList) { _validatorList = new address[](validatorCount); for (uint _i = 0; _i < _validatorList.length; _i++) { _validatorList[_i] = _validator[_i]; @@ -324,7 +341,9 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { * @dev Updates the validator set based on the validator candidates from the Staking contract. */ function _updateValidatorSet() internal { + console.log("\t3.1", gasleft()); (address[] memory _candidates, uint256[] memory _weights) = IStaking(_stakingContract).getCandidateWeights(); + console.log("\t3.2", gasleft()); uint256 _newLength = _candidates.length; for (uint256 _i; _i < _candidates.length; _i++) { if (_jailed(_candidates[_i])) { @@ -333,13 +352,16 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _weights[_i] = _weights[_newLength]; } } + console.log("\t3.3", gasleft()); assembly { mstore(_candidates, _newLength) mstore(_weights, _newLength) } + console.log("\t3.4", gasleft()); _candidates = Sorting.sort(_candidates, _weights); + console.log("\t3.5", gasleft()); uint256 _newValidatorCount = Math.min(_maxValidatorNumber, _candidates.length); // TODO: pick at least M governers as validators @@ -356,6 +378,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _validatorMap[_newValidator] = true; _validator[_i] = _newValidator; } + console.log("\t3.6", gasleft()); validatorCount = _newValidatorCount; _lastUpdatedBlock = block.number; diff --git a/contracts/staking/DPoStaking.sol b/contracts/staking/DPoStaking.sol index 248c9f800..cb2503b8c 100644 --- a/contracts/staking/DPoStaking.sol +++ b/contracts/staking/DPoStaking.sol @@ -126,17 +126,26 @@ contract DPoStaking is IStaking, StakingManager, Initializable { /** * @inheritdoc IStaking */ - function recordRewardForDelegators(address _consensusAddr, uint256 _reward) external payable onlyValidatorContract { - _recordRewardForDelegators(_consensusAddr, _reward); + function recordReward(address _consensusAddr, uint256 _reward) external payable onlyValidatorContract { + _recordReward(_consensusAddr, _reward); } /** * @inheritdoc IStaking */ - function settleRewardPoolForDelegators(address _consensusAddr) external onlyValidatorContract { + function settleRewardPool(address _consensusAddr) external onlyValidatorContract { _onPoolSettled(_consensusAddr); } + /** + * @inheritdoc IStaking + */ + function settleMultipleRewardPools(address[] calldata _consensusAddrs) external onlyValidatorContract { + for (uint _i = 0; _i < _consensusAddrs.length; _i++) { + _onPoolSettled(_consensusAddrs[_i]); + } + } + /** * @inheritdoc IStaking */ diff --git a/contracts/staking/RewardCalculation.sol b/contracts/staking/RewardCalculation.sol index 04dbfa803..4db35e63c 100644 --- a/contracts/staking/RewardCalculation.sol +++ b/contracts/staking/RewardCalculation.sol @@ -197,7 +197,7 @@ abstract contract RewardCalculation { * @notice This method should not be called after the pending pool is sinked. * */ - function _recordRewardForDelegators(address _poolAddr, uint256 _reward) internal { + function _recordReward(address _poolAddr, uint256 _reward) internal { PendingPool storage _pool = _pendingPool[_poolAddr]; uint256 _accumulatedRps = _pool.accumulatedRps + (_reward * 1e18) / totalBalance(_poolAddr); _pool.accumulatedRps = _accumulatedRps; diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 9ddec963a..494a27410 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -491,9 +491,11 @@ contract Staking is IStaking, Initializable { function getValidatorCandidates() external view override returns (ValidatorCandidate[] memory candidates) {} - function recordRewardForDelegators(address _consensusAddr, uint256 _reward) external payable override {} + function recordReward(address _consensusAddr, uint256 _reward) external payable override {} - function settleRewardPoolForDelegators(address _consensusAddr) external override {} + function settleRewardPool(address _consensusAddr) external override {} + + function settleMultipleRewardPools(address[] calldata _consensusAddrs) external override {} function sinkPendingReward(address _consensusAddr) external override {} diff --git a/contracts/validator/ValidatorSet.sol b/contracts/validator/ValidatorSet.sol index 815b5444d..dd66bc350 100644 --- a/contracts/validator/ValidatorSet.sol +++ b/contracts/validator/ValidatorSet.sol @@ -93,7 +93,7 @@ contract ValidatorSet is IValidatorSet, ValidatorSetCore { if (_validator.jailed) { emit DeprecatedDeposit(_valAddr, _value); } else { - stakingContract.recordRewardForDelegators(_valAddr, _value); + stakingContract.recordReward(_valAddr, _value); } } diff --git a/hardhat.config.ts b/hardhat.config.ts index 2ee51bc46..eade88eb2 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -64,7 +64,7 @@ const config: HardhatUserConfig = { accounts: { mnemonic: DEFAULT_MNEMONIC, count: 100, - accountsBalance: '1000000000000000000000000000' // 1B RON + accountsBalance: '1000000000000000000000000000', // 1B RON }, }, 'ronin-testnet': testnet, diff --git a/src/addresses.ts b/src/addresses.ts index aa55933c8..c1c5ef797 100644 --- a/src/addresses.ts +++ b/src/addresses.ts @@ -6,4 +6,4 @@ export enum Network { export type LiteralNetwork = Network | string; -export const defaultAddress = '0x0000000000000000000000000000000000000000'; \ No newline at end of file +export const defaultAddress = '0x0000000000000000000000000000000000000000'; diff --git a/src/deploy/dpostaking.ts b/src/deploy/dpostaking.ts index e7b2fea2f..3edcfe9b1 100644 --- a/src/deploy/dpostaking.ts +++ b/src/deploy/dpostaking.ts @@ -11,7 +11,6 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const logicContract = await deployments.get('DPoStakingLogic'); const data = new DPoStaking__factory().interface.encodeFunctionData('initialize', [ - stakingConfig[network.name]!.unstakingOnHoldBlocksNum, stakingConfig[network.name]!.validatorContract, stakingConfig[network.name]!.governanceAdminContract, stakingConfig[network.name]!.maxValidatorCandidate, diff --git a/src/deploy/logic/dpostaking.ts b/src/deploy/logic/dpostaking.ts new file mode 100644 index 000000000..945bc9efa --- /dev/null +++ b/src/deploy/logic/dpostaking.ts @@ -0,0 +1,17 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + await deploy('DPoStakingLogic', { + contract: 'DPoStaking', + from: deployer, + log: true, + }); +}; + +deploy.tags = ['DPoStakingLogic']; +deploy.dependencies = ['ProxyAdmin']; + +export default deploy; diff --git a/src/script/dpostaking.ts b/src/script/dpostaking.ts index c0e2e7775..ec6db4c0a 100644 --- a/src/script/dpostaking.ts +++ b/src/script/dpostaking.ts @@ -4,7 +4,6 @@ import { LiteralNetwork, Network } from '../addresses'; interface DPoStakingConf { [network: LiteralNetwork]: | { - unstakingOnHoldBlocksNum: BigNumberish; validatorContract: BigNumberish; governanceAdminContract: BigNumberish; maxValidatorCandidate: BigNumberish; From abfd5d236479b0600b840cf49c1baa328271af87 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 6 Sep 2022 17:43:27 +0700 Subject: [PATCH 074/190] [Staking] add test --- test/staking/DPoStaking.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/staking/DPoStaking.test.ts b/test/staking/DPoStaking.test.ts index d3f91ce4f..b53f30ee5 100644 --- a/test/staking/DPoStaking.test.ts +++ b/test/staking/DPoStaking.test.ts @@ -111,7 +111,7 @@ describe('DPoStaking test', () => { ); await proxyContract.deployed(); stakingContract = DPoStaking__factory.connect(proxyContract.address, deployer); - expect(stakingContractAddr.toLowerCase()).eq(proxyContract.address.toLowerCase()); + expect(stakingContractAddr.toLowerCase()).eq(stakingContract.address.toLowerCase()); }); describe('Validator candidate test', () => { @@ -137,6 +137,12 @@ describe('DPoStaking test', () => { expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance); }); + it('Should not be able to propose validator again', async () => { + await expect( + stakingContract.connect(poolAddr).proposeValidator(poolAddr.address, poolAddr.address, 0) + ).revertedWith('StakingManager: query for existed candidate'); + }); + it('Should not be able to stake with empty value', async () => { await expect(stakingContract.stake(poolAddr.address, { value: 0 })).revertedWith( 'StakingManager: query with empty value' From 13bfdc5b2bd8bc139251f41d51a685aeff3c01b3 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 6 Sep 2022 18:39:01 +0700 Subject: [PATCH 075/190] [Staking] use bulk fn & update test --- contracts/interfaces/IStaking.sol | 15 +---- contracts/mocks/staking/MockStaking.sol | 4 +- .../staking/MockValidatorSetForStaking.sol | 2 +- contracts/staking/DPoStaking.sol | 14 ++-- contracts/staking/RewardCalculation.sol | 23 ++++--- contracts/staking/Staking.sol | 4 +- test/staking/CoreStaking.test.ts | 66 +++++++++---------- 7 files changed, 58 insertions(+), 70 deletions(-) diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 869c8ff61..6910787d7 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -125,21 +125,10 @@ interface IStaking { * Requirements: * - The method caller is validator contract. * - * Emits the `SettledPoolUpdated` event. + * Emits the `SettledPoolsUpdated` event. * */ - function settleRewardPool(address _consensusAddr) external; - - /** - * @dev Settles the pending pool and allocates rewards for the pool `_consensusAddr`. - * - * Requirements: - * - The method caller is validator contract. - * - * Emits the `SettledPoolUpdated` event. - * - */ - function settleMultipleRewardPools(address[] calldata _consensusAddrs) external; + function settleRewardPools(address[] calldata _consensusAddrs) external; /** * @dev Handles when the pending reward pool of the validator is sinked. diff --git a/contracts/mocks/staking/MockStaking.sol b/contracts/mocks/staking/MockStaking.sol index 73c1ef7fa..0d79cc82c 100644 --- a/contracts/mocks/staking/MockStaking.sol +++ b/contracts/mocks/staking/MockStaking.sol @@ -49,8 +49,8 @@ contract MockStaking is RewardCalculation { _recordReward(poolAddr, _rewardAmount); } - function commitRewardPool() external { - _onPoolSettled(poolAddr); + function commitRewardPool(address[] calldata _addrList) external { + _onPoolsSettled(_addrList); } function increaseAccumulatedRps(uint256 _amount) external { diff --git a/contracts/mocks/staking/MockValidatorSetForStaking.sol b/contracts/mocks/staking/MockValidatorSetForStaking.sol index f5f6dbac6..0f06a2608 100644 --- a/contracts/mocks/staking/MockValidatorSetForStaking.sol +++ b/contracts/mocks/staking/MockValidatorSetForStaking.sol @@ -29,7 +29,7 @@ contract MockValidatorSetForStaking is IValidatorSet { } function settledReward(address[] calldata _validatorList) external { - stakingContract.settleMultipleRewardPools(_validatorList); + stakingContract.settleRewardPools(_validatorList); } function slashMisdemeanor(address _validator) external override { diff --git a/contracts/staking/DPoStaking.sol b/contracts/staking/DPoStaking.sol index cb2503b8c..995229dfd 100644 --- a/contracts/staking/DPoStaking.sol +++ b/contracts/staking/DPoStaking.sol @@ -133,17 +133,11 @@ contract DPoStaking is IStaking, StakingManager, Initializable { /** * @inheritdoc IStaking */ - function settleRewardPool(address _consensusAddr) external onlyValidatorContract { - _onPoolSettled(_consensusAddr); - } - - /** - * @inheritdoc IStaking - */ - function settleMultipleRewardPools(address[] calldata _consensusAddrs) external onlyValidatorContract { - for (uint _i = 0; _i < _consensusAddrs.length; _i++) { - _onPoolSettled(_consensusAddrs[_i]); + function settleRewardPools(address[] calldata _consensusAddrs) external onlyValidatorContract { + if (_consensusAddrs.length == 0) { + return; } + _onPoolsSettled(_consensusAddrs); } /** diff --git a/contracts/staking/RewardCalculation.sol b/contracts/staking/RewardCalculation.sol index 4db35e63c..959826324 100644 --- a/contracts/staking/RewardCalculation.sol +++ b/contracts/staking/RewardCalculation.sol @@ -11,7 +11,7 @@ pragma solidity ^0.8.9; */ abstract contract RewardCalculation { /// @dev Emitted when the settled pool is updated. - event SettledPoolUpdated(address poolAddress, uint256 accumulatedRps); + event SettledPoolsUpdated(address[] poolAddress, uint256[] accumulatedRps); /// @dev Emitted when the pending pool is updated. event PendingPoolUpdated(address poolAddress, uint256 accumulatedRps); /// @dev Emitted when the fields to calculate settled reward for the user is updated. @@ -224,17 +224,24 @@ abstract contract RewardCalculation { /** * @dev Handles when the pool `_poolAddr` is settled. * - * Emits the `SettledPoolUpdated` event. + * Emits the `SettledPoolsUpdated` event. * * @notice This method should be called once in the end of each period. * */ - function _onPoolSettled(address _poolAddr) internal { - uint256 _accumulatedRps = _pendingPool[_poolAddr].accumulatedRps; - SettledPool storage _sPool = _settledPool[_poolAddr]; - _sPool.accumulatedRps = _accumulatedRps; - _sPool.lastSyncBlock = block.number; - emit SettledPoolUpdated(_poolAddr, _accumulatedRps); + function _onPoolsSettled(address[] calldata _poolList) internal { + uint256[] memory _accumulatedRpsList = new uint256[](_poolList.length); + address _poolAddr; + uint256 _accumulatedRps; + for (uint256 _i; _i < _poolList.length; _i++) { + _poolAddr = _poolList[_i]; + _accumulatedRps = _accumulatedRpsList[_i] = _pendingPool[_poolAddr].accumulatedRps; + + SettledPool storage _sPool = _settledPool[_poolAddr]; + _sPool.accumulatedRps = _accumulatedRps; + _sPool.lastSyncBlock = block.number; + } + emit SettledPoolsUpdated(_poolList, _accumulatedRpsList); } /** diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 494a27410..3f252c5c4 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -493,9 +493,7 @@ contract Staking is IStaking, Initializable { function recordReward(address _consensusAddr, uint256 _reward) external payable override {} - function settleRewardPool(address _consensusAddr) external override {} - - function settleMultipleRewardPools(address[] calldata _consensusAddrs) external override {} + function settleRewardPools(address[] calldata _consensusAddrs) external override {} function sinkPendingReward(address _consensusAddr) external override {} diff --git a/test/staking/CoreStaking.test.ts b/test/staking/CoreStaking.test.ts index 6a4d20fe7..85cf023c4 100644 --- a/test/staking/CoreStaking.test.ts +++ b/test/staking/CoreStaking.test.ts @@ -97,12 +97,12 @@ describe('Core Staking test', () => { await stakingContract.stake(userB.address, 100); await network.provider.send('evm_mine'); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await network.provider.send('evm_mine'); await network.provider.send('evm_mine'); @@ -113,13 +113,13 @@ describe('Core Staking test', () => { await network.provider.send('evm_mine'); await expectLocalCalculationRight(); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); await stakingContract.unstake(userA.address, 200); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); @@ -129,7 +129,7 @@ describe('Core Staking test', () => { await local.recordReward(0); await expectLocalCalculationRight(); - await stakingContract.commitRewardPool(); + await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); local.commitRewardPool(); @@ -142,18 +142,18 @@ describe('Core Staking test', () => { await expectLocalCalculationRight(); await local.recordReward(0); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); await stakingContract.stake(userA.address, 300); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); @@ -163,7 +163,7 @@ describe('Core Staking test', () => { local.slash(); await expectLocalCalculationRight(); - await stakingContract.recordRewardForDelegators(0); + await stakingContract.recordReward(0); await network.provider.send('evm_mine'); await local.recordReward(0); await expectLocalCalculationRight(); @@ -179,7 +179,7 @@ describe('Core Staking test', () => { await network.provider.send('evm_mine'); await expectLocalCalculationRight(); - await stakingContract.commitRewardPool(); + await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await expectLocalCalculationRight(); @@ -192,19 +192,19 @@ describe('Core Staking test', () => { await expectLocalCalculationRight(); await stakingContract.claimReward(userA.address); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); local.claimRewardForA(); await expectLocalCalculationRight(); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); await stakingContract.stake(userA.address, 300); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); @@ -214,7 +214,7 @@ describe('Core Staking test', () => { local.slash(); await expectLocalCalculationRight(); - await stakingContract.recordRewardForDelegators(0); + await stakingContract.recordReward(0); await network.provider.send('evm_mine'); await local.recordReward(0); await expectLocalCalculationRight(); @@ -231,7 +231,7 @@ describe('Core Staking test', () => { await expectLocalCalculationRight(); await stakingContract.claimReward(userB.address); - await stakingContract.commitRewardPool(); + await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); local.claimRewardForB(); @@ -239,8 +239,8 @@ describe('Core Staking test', () => { }); it('Should be able to calculate right reward after claiming', async () => { - await stakingContract.recordRewardForDelegators(1000); - await stakingContract.commitRewardPool(); + await stakingContract.recordReward(1000); + await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await local.recordReward(1000); @@ -248,7 +248,7 @@ describe('Core Staking test', () => { await expectLocalCalculationRight(); await stakingContract.claimReward(userA.address); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); local.claimRewardForA(); @@ -260,7 +260,7 @@ describe('Core Staking test', () => { await expectLocalCalculationRight(); await stakingContract.claimReward(userB.address); - await stakingContract.commitRewardPool(); + await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); local.claimRewardForB(); @@ -273,30 +273,30 @@ describe('Core Staking test', () => { await network.provider.send('evm_mine'); await stakingContract.stake(userA.address, 300); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); await stakingContract.stake(userB.address, 200); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); await stakingContract.unstake(userB.address, 200); await stakingContract.unstake(userA.address, 400); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); @@ -309,7 +309,7 @@ describe('Core Staking test', () => { await expectLocalCalculationRight(); await stakingContract.unstake(userA.address, 200); - await stakingContract.commitRewardPool(); + await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); local.commitRewardPool(); @@ -329,15 +329,15 @@ describe('Core Staking test', () => { await stakingContract.stake(userA.address, 100); await stakingContract.stake(userB.address, 200); await stakingContract.unstake(userB.address, 200); - await stakingContract.recordRewardForDelegators(1000); - await stakingContract.commitRewardPool(); + await stakingContract.recordReward(1000); + await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await local.recordReward(1000); local.commitRewardPool(); await expectLocalCalculationRight(); - await stakingContract.recordRewardForDelegators(1000); + await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); @@ -347,21 +347,21 @@ describe('Core Staking test', () => { local.slash(); await expectLocalCalculationRight(); - await stakingContract.commitRewardPool(); + await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await expectLocalCalculationRight(); - await stakingContract.recordRewardForDelegators(1000); - await stakingContract.commitRewardPool(); + await stakingContract.recordReward(1000); + await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await local.recordReward(1000); local.commitRewardPool(); await expectLocalCalculationRight(); - await stakingContract.recordRewardForDelegators(1000); - await stakingContract.commitRewardPool(); + await stakingContract.recordReward(1000); + await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await local.recordReward(1000); From 039c49b53cb6db60485f7fa585b2bd5bb7b58e67 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 6 Sep 2022 18:46:22 +0700 Subject: [PATCH 076/190] [RoninValidatorSet] Remove console log --- .../ronin-validator/RoninValidatorSet.sol | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index ed101c40f..521645b0e 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.9; -import "hardhat/console.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "../interfaces/ISlashIndicator.sol"; @@ -127,7 +126,6 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { * @inheritdoc IRoninValidatorSet */ function wrapUpEpoch() external payable override onlyCoinbase whenEpochEnding { - console.log("0", gasleft()); IStaking _staking = IStaking(_stakingContract); address _validatorAddr; @@ -135,7 +133,6 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { uint256 _period = periodOf(block.number); bool _periodEnding = periodEndingAt(block.number); - console.log("1", gasleft()); address[] memory _validators = getValidators(); for (uint _i = 0; _i < _validators.length; _i++) { _validatorAddr = _validators[_i]; @@ -161,23 +158,17 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { // TODO: emit events } - console.log("2", gasleft()); - // if (_periodEnding) {} - - ISlashIndicator(_slashIndicatorContract).resetCounters(_validators); - console.log("2.a", gasleft()); - _staking.settleMultipleRewardPools(_validators); - - console.log("3", gasleft()); + if (_periodEnding) { + ISlashIndicator(_slashIndicatorContract).resetCounters(_validators); + } + _staking.settleRewardPools(_validators); if (_delegatingAmount > 0) { // TODO(Thor): use `call` to transfer reward with reentrancy gruard require(payable(address(_staking)).send(_delegatingAmount), "RoninValidatorSet: could not transfer RON"); } - console.log("4", gasleft()); _updateValidatorSet(); - console.log("5", gasleft()); } /** @@ -341,9 +332,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { * @dev Updates the validator set based on the validator candidates from the Staking contract. */ function _updateValidatorSet() internal { - console.log("\t3.1", gasleft()); (address[] memory _candidates, uint256[] memory _weights) = IStaking(_stakingContract).getCandidateWeights(); - console.log("\t3.2", gasleft()); uint256 _newLength = _candidates.length; for (uint256 _i; _i < _candidates.length; _i++) { if (_jailed(_candidates[_i])) { @@ -352,16 +341,13 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _weights[_i] = _weights[_newLength]; } } - console.log("\t3.3", gasleft()); assembly { mstore(_candidates, _newLength) mstore(_weights, _newLength) } - console.log("\t3.4", gasleft()); _candidates = Sorting.sort(_candidates, _weights); - console.log("\t3.5", gasleft()); uint256 _newValidatorCount = Math.min(_maxValidatorNumber, _candidates.length); // TODO: pick at least M governers as validators @@ -378,7 +364,6 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _validatorMap[_newValidator] = true; _validator[_i] = _newValidator; } - console.log("\t3.6", gasleft()); validatorCount = _newValidatorCount; _lastUpdatedBlock = block.number; From 3141bb195ea22a2d61c3756917812c27e26207c7 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 6 Sep 2022 18:46:54 +0700 Subject: [PATCH 077/190] [RoninValidatorSet] add unit test --- test/validator/RoninValidatorSet.test.ts | 226 +++++++++++++++++------ 1 file changed, 166 insertions(+), 60 deletions(-) diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index b6c454c28..3012c6872 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -1,60 +1,166 @@ -// import { expect } from 'chai'; -// import { ethers } from 'hardhat'; -// import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; - -// import { MockSorting, MockSorting__factory } from '../../src/types'; - -// let quickSortContract: MockSorting; - -// let deployer: SignerWithAddress; -// let signers: SignerWithAddress[]; - -// const stats: { items: number; gasUsed: string; }[] = []; - -// const runSortWithNRecords = async (numOfRecords: number) => { -// const balances = []; -// const mapV = new Map(); -// for (let i = 0; i < numOfRecords; ++i) { -// let value = 0; -// do { -// value = Math.floor(Math.random() * numOfRecords * 10_000); -// } while (!!mapV.get(value)); -// mapV.set(value, true); -// balances.push({ -// address: signers[i].address, -// value, -// }); -// } - -// balances.sort((a, b) => (a.value < b.value ? 1 : -1)); - -// const gasUsed = await quickSortContract.estimateGas.sortAddressesAndValues( -// balances.map((_) => _.address), -// balances.map((_) => _.value) -// ); - -// const sorted = await quickSortContract.sortAddressesAndValues( -// balances.map((_) => _.address), -// balances.map((_) => _.value) -// ); - -// expect(sorted).eql(balances.map((_) => _.address)); -// return gasUsed.toString(); -// }; - -// describe('Quick sort test', () => { -// before(async () => { -// [deployer, ...signers] = await ethers.getSigners(); -// quickSortContract = await new MockSorting__factory(deployer).deploy(); -// }); - -// after(() => { -// console.table(stats); -// }); - -// for (let i = 1; i < 100; i++) { -// i % 10 == 9 && it(`Should sort correctly on ${i} records`, async () => { -// stats.push({ items: i, gasUsed: await runSortWithNRecords(i) }); -// }); -// } -// }); +import { expect } from 'chai'; +import { ethers, network } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; + +import { + DPoStaking, + MockSorting, + MockSorting__factory, + RoninValidatorSet, + MockRoninValidatorSet, + MockRoninValidatorSet__factory, + DPoStaking__factory, + TransparentUpgradeableProxy__factory, + SlashIndicator, + SlashIndicator__factory, +} from '../../src/types'; +import { BigNumber } from 'ethers'; + +let roninValidatorSet: MockRoninValidatorSet; +let stakingContract: DPoStaking; +let slashIndicator: SlashIndicator; + +let coinbase: SignerWithAddress; +let deployer: SignerWithAddress; +let governanceAdmin: SignerWithAddress; +let proxyAdmin: SignerWithAddress; +let validatorCandidates: SignerWithAddress[]; + +const slashFelonyAmount = 100; +const slashDoubleSignAmount = 1000; +const maxValidatorNumber = 4; +const minValidatorBalance = BigNumber.from(2); + +const mineTxs = async (fn: () => Promise) => { + await network.provider.send('evm_setAutomine', [false]); + await fn(); + await network.provider.send('evm_mine'); + await network.provider.send('evm_setAutomine', [true]); +}; + +describe('Ronin Validator Set test', () => { + before(async () => { + [coinbase, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + validatorCandidates = validatorCandidates.slice(0, 5); + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + + const nonce = await deployer.getTransactionCount(); + const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 1 }); + const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 3 }); + + slashIndicator = await new SlashIndicator__factory(deployer).deploy(roninValidatorSetAddr); + await slashIndicator.deployed(); + + roninValidatorSet = await new MockRoninValidatorSet__factory(deployer).deploy( + governanceAdmin.address, + slashIndicator.address, + stakingContractAddr, + maxValidatorNumber, + slashFelonyAmount, + slashDoubleSignAmount + ); + await roninValidatorSet.deployed(); + + const logicContract = await new DPoStaking__factory(deployer).deploy(); + await logicContract.deployed(); + + const proxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + logicContract.address, + proxyAdmin.address, + logicContract.interface.encodeFunctionData('initialize', [ + roninValidatorSet.address, + governanceAdmin.address, + 100, + minValidatorBalance, + ]) + ); + await proxyContract.deployed(); + stakingContract = DPoStaking__factory.connect(proxyContract.address, deployer); + + expect(roninValidatorSetAddr.toLowerCase()).eq(roninValidatorSet.address.toLowerCase()); + expect(stakingContractAddr.toLowerCase()).eq(stakingContract.address.toLowerCase()); + }); + + after(async () => { + await network.provider.send('hardhat_setCoinbase', [ethers.constants.AddressZero]); + }); + + it('Should not be able to wrap up epoch using unauthorized account', async () => { + await expect(roninValidatorSet.connect(deployer).wrapUpEpoch()).revertedWith( + 'RoninValidatorSet: method caller must be coinbase' + ); + }); + + it('Should not be able to wrap up epoch when the epoch is not ending', async () => { + await expect(roninValidatorSet.connect(coinbase).wrapUpEpoch()).revertedWith( + 'RoninValidatorSet: only allowed at the end of epoch' + ); + }); + + it('Should be able to wrap up epoch when the epoch is ending', async () => { + await mineTxs(async () => { + await roninValidatorSet.endEpoch(); + await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); + expect(await roninValidatorSet.getValidators()).have.same.members([]); + }); + + it('Should be able to wrap up epoch and sync validator set from staking contract', async () => { + for (let i = 0; i <= 3; i++) { + await stakingContract + .connect(validatorCandidates[i]) + .proposeValidator(validatorCandidates[i].address, validatorCandidates[i].address, 2_00, { + value: minValidatorBalance.add(i), + }); + } + + await mineTxs(async () => { + await roninValidatorSet.endEpoch(); + await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); + + expect(await roninValidatorSet.getValidators()).have.same.members( + validatorCandidates + .slice(0, 4) + .reverse() + .map((_) => _.address) + ); + }); + + it(`Should be able to wrap up epoch and pick top ${maxValidatorNumber} to be validators`, async () => { + await stakingContract + .connect(coinbase) + .proposeValidator(coinbase.address, coinbase.address, 1_00 /* 1% */, { value: 100 }); + for (let i = 4; i < validatorCandidates.length; i++) { + await stakingContract + .connect(validatorCandidates[i]) + .proposeValidator(validatorCandidates[i].address, validatorCandidates[i].address, 2_00, { + value: minValidatorBalance.add(i), + }); + } + expect((await stakingContract.getValidatorCandidates()).length).eq(validatorCandidates.length + 1); + + await mineTxs(async () => { + await roninValidatorSet.endEpoch(); + await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); + + expect(await roninValidatorSet.getValidators()).have.same.members([ + coinbase.address, + ...validatorCandidates + .slice(2) + .reverse() + .map((_) => _.address), + ]); + }); + + it('Should not be able to submit block reward using unauthorized account', async () => { + await expect(roninValidatorSet.submitBlockReward()).revertedWith( + 'RoninValidatorSet: method caller must be coinbase' + ); + }); + + it('Should be able to submit block reward using coinbase account', async () => { + await roninValidatorSet.connect(coinbase).submitBlockReward(); + }); +}); From d8a60ba9a1983ea7ecf37d643e277fffc7a69fab Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 6 Sep 2022 19:01:08 +0700 Subject: [PATCH 078/190] [SlashIndicator] Fix slash function. Add test. --- contracts/mocks/MockEmptyValidatorSet.sol | 25 +++ contracts/slash/SlashIndicator.sol | 4 + test/slash/SlashIndicator.test.ts | 228 ++++++++++++++++++++++ 3 files changed, 257 insertions(+) create mode 100644 contracts/mocks/MockEmptyValidatorSet.sol create mode 100644 test/slash/SlashIndicator.test.ts diff --git a/contracts/mocks/MockEmptyValidatorSet.sol b/contracts/mocks/MockEmptyValidatorSet.sol new file mode 100644 index 000000000..e2a9edc01 --- /dev/null +++ b/contracts/mocks/MockEmptyValidatorSet.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../interfaces/ISlashIndicator.sol"; + +contract MockEmptyValidatorSet { + ISlashIndicator private __slashingContract; + + function _setSlashingContract() internal view virtual returns (ISlashIndicator) { + return __slashingContract; + } + + function setSlashingContract(ISlashIndicator _addr) external { + __slashingContract = _addr; + } + + function slashFelony(address _addr) external {} + + function slashMisdemeanor(address _addr) external {} + + function resetCounters(address[] calldata _addr) external { + __slashingContract.resetCounters(_addr); + } +} diff --git a/contracts/slash/SlashIndicator.sol b/contracts/slash/SlashIndicator.sol index b8fa40174..10492c8d5 100644 --- a/contracts/slash/SlashIndicator.sol +++ b/contracts/slash/SlashIndicator.sol @@ -48,6 +48,10 @@ contract SlashIndicator is ISlashIndicator { * @inheritdoc ISlashIndicator */ function slash(address _validatorAddr) external override onlyCoinbase oncePerBlock { + if (msg.sender == _validatorAddr) { + return; + } + uint256 _count = ++_unavailabilityIndicator[_validatorAddr]; // Slashs the validator as either the fenoly or the misdemeanor diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts new file mode 100644 index 000000000..d68d5b0f1 --- /dev/null +++ b/test/slash/SlashIndicator.test.ts @@ -0,0 +1,228 @@ +import { expect } from 'chai'; +import { ethers, network } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; + +import { + SlashIndicator, + SlashIndicator__factory, + MockEmptyValidatorSet, + MockEmptyValidatorSet__factory, +} from '../../src/types'; +import { Address } from 'hardhat-deploy/dist/types'; + +let slashContract: SlashIndicator; + +let deployer: SignerWithAddress; +let dummyValidatorsContract: MockEmptyValidatorSet; +let vagabond: SignerWithAddress; +let coinbases: SignerWithAddress[]; +let defaultCoinbase: Address; +let localIndicators: number[]; + +enum SlashType { + UNKNOWN, + MISDEMAENOR, + FELONY, + DOUBLE_SIGNING, +} + +const getBlockNumber = async () => { + return network.provider.send('eth_blockNumber'); +}; + +const resetCoinbase = async () => { + await network.provider.send('hardhat_setCoinbase', [defaultCoinbase]); +}; + +const validateTwoObjects = async (definedObj: any, resultObj: any) => { + let key: keyof typeof definedObj; + for (key in definedObj) { + const definedVal = definedObj[key]; + const resultVal = resultObj[key]; + + if (Array.isArray(resultVal)) { + for (let i = 0; i < resultVal.length; i++) { + await expect(resultVal[i]).to.eq(definedVal[i]); + } + } else { + await expect(resultVal).to.eq(definedVal); + } + } +}; + +const setLocalCounterForValidatorAt = async (_index: number, _value: number) => { + localIndicators[_index] = _value; +}; + +const resetLocalCounterForValidatorAt = async (_index: number) => { + localIndicators[_index] = 0; +}; + +const validateIndicatorAt = async (_index: number) => { + expect(localIndicators[_index]).to.eq(await slashContract.getSlashIndicator(coinbases[_index].address)); +}; + +const doSlash = async (slasher: SignerWithAddress, slashee: SignerWithAddress) => { + return slashContract.connect(slasher).slash(slashee.address); +}; + +describe('Slash indicator test', () => { + let felonyThreshold: number; + let misdemeanorThreshold: number; + + before(async () => { + [deployer, vagabond, ...coinbases] = await ethers.getSigners(); + + localIndicators = Array(coinbases.length).fill(0); + + dummyValidatorsContract = await new MockEmptyValidatorSet__factory(deployer).deploy(); + slashContract = await new SlashIndicator__factory(deployer).deploy(dummyValidatorsContract.address); + await dummyValidatorsContract.connect(deployer).setSlashingContract(slashContract.address); + + let thresholds = await slashContract.getSlashThresholds(); + felonyThreshold = thresholds[0].toNumber(); + misdemeanorThreshold = thresholds[1].toNumber(); + + defaultCoinbase = await network.provider.send('eth_coinbase'); + console.log('Default coinbase:', defaultCoinbase); + }); + + describe('Single flow test', async () => { + describe('Unauthorized test', async () => { + it('Should non-coinbase cannot call slash', async () => { + await expect(slashContract.connect(vagabond).slash(coinbases[0].address)).to.revertedWith( + 'SlashIndicator: method caller is not the coinbase' + ); + }); + + it('Should non-validatorContract cannot call reset counter', async () => { + await expect(slashContract.connect(vagabond).resetCounters([coinbases[0].address])).to.revertedWith( + 'SlashIndicator: method caller is not the validator contract' + ); + }); + }); + + describe('Slash method: recording', async () => { + it('Should slash a validator successfully', async () => { + let slasherIdx = 0; + let slasheeIdx = 1; + await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); + + let tx = await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); + expect(tx).to.not.emit(slashContract, 'ValidatorSlashed'); + setLocalCounterForValidatorAt(slasheeIdx, 1); + validateIndicatorAt(slasheeIdx); + }); + + it('Should validator not be able to slash themselves', async () => { + let slasherIdx = 0; + let tx = await doSlash(coinbases[slasherIdx], coinbases[slasherIdx]); + expect(tx).to.not.emit(slashContract, 'ValidatorSlashed'); + + await resetLocalCounterForValidatorAt(slasherIdx); + await validateIndicatorAt(slasherIdx); + }); + + it('Should not able to slash twice in one block', async () => { + let slasherIdx = 0; + let slasheeIdx = 2; + await network.provider.send('evm_setAutomine', [false]); + await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); + let tx = doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); + await network.provider.send('evm_mine'); + await network.provider.send('evm_setAutomine', [true]); + + expect(tx).to.be.revertedWith('SlashIndicator: cannot slash twice in one block'); + + await setLocalCounterForValidatorAt(slasheeIdx, 1); + await validateIndicatorAt(slasheeIdx); + }); + }); + + describe('Slash method: recording and call to validator set', async () => { + it('Should sync with validator set for felony', async () => { + let tx; + let slasherIdx = 0; + let slasheeIdx = 3; + + await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); + + for (let i = 0; i < felonyThreshold; i++) { + tx = await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); + } + expect(tx).to.emit(slashContract, 'ValidatorSlashed').withArgs(coinbases[1].address, SlashType.FELONY); + await setLocalCounterForValidatorAt(slasheeIdx, felonyThreshold); + await validateIndicatorAt(slasheeIdx); + }); + + it('Should sync with validator set for misdemeanor', async () => { + let tx; + let slasherIdx = 1; + let slasheeIdx = 4; + await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); + + for (let i = 0; i < misdemeanorThreshold; i++) { + tx = await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); + } + expect(tx).to.emit(slashContract, 'ValidatorSlashed').withArgs(coinbases[1].address, SlashType.MISDEMAENOR); + await setLocalCounterForValidatorAt(slasheeIdx, misdemeanorThreshold); + await validateIndicatorAt(slasheeIdx); + }); + }); + + describe('Reseting counter', async () => { + it('Should validator set contract reset counter for one validator', async () => { + let tx; + let slasherIdx = 0; + let slasheeIdx = 5; + let numberOfSlashing = felonyThreshold - 1; + await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); + + for (let i = 0; i < numberOfSlashing; i++) { + await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); + } + await setLocalCounterForValidatorAt(slasheeIdx, numberOfSlashing); + await validateIndicatorAt(slasheeIdx); + + await resetCoinbase(); + + tx = await dummyValidatorsContract.resetCounters([coinbases[slasheeIdx].address]); + expect(tx).to.emit(slashContract, 'UnavailabilityIndicatorReset').withArgs(coinbases[slasheeIdx].address); + + await resetLocalCounterForValidatorAt(slasheeIdx); + await validateIndicatorAt(slasheeIdx); + }); + + it('Should validator set contract reset counter for multiple validators', async () => { + let tx; + let slasherIdx = 0; + let slasheeIdxs = [6, 7, 8, 9, 10]; + let numberOfSlashing = felonyThreshold - 1; + await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); + + for (let i = 0; i < numberOfSlashing; i++) { + for (let j = 0; j < slasheeIdxs.length; j++) { + await doSlash(coinbases[slasherIdx], coinbases[slasheeIdxs[j]]); + } + } + + for (let j = 0; j < slasheeIdxs.length; j++) { + await setLocalCounterForValidatorAt(slasheeIdxs[j], numberOfSlashing); + await validateIndicatorAt(slasheeIdxs[j]); + } + + await resetCoinbase(); + + tx = await dummyValidatorsContract.resetCounters(slasheeIdxs.map((_) => coinbases[_].address)); + + for (let j = 0; j < slasheeIdxs.length; j++) { + expect(tx).to.emit(slashContract, 'UnavailabilityIndicatorReset').withArgs(coinbases[slasheeIdxs[j]].address); + await resetLocalCounterForValidatorAt(slasheeIdxs[j]); + await validateIndicatorAt(slasheeIdxs[j]); + } + }); + }); + }); + + describe('Integration test', async () => {}); +}); From 3977f914828d61e8def4f5e341ec75fb67256c0e Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 7 Sep 2022 10:52:34 +0700 Subject: [PATCH 079/190] [RoninValidatorSet] add tests --- .../mocks/validator/MockSlashIndicator.sol | 42 ++++++++ test/validator/RoninValidatorSet.test.ts | 95 ++++++++++++++++--- 2 files changed, 122 insertions(+), 15 deletions(-) create mode 100644 contracts/mocks/validator/MockSlashIndicator.sol diff --git a/contracts/mocks/validator/MockSlashIndicator.sol b/contracts/mocks/validator/MockSlashIndicator.sol new file mode 100644 index 000000000..5e197a127 --- /dev/null +++ b/contracts/mocks/validator/MockSlashIndicator.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../../interfaces/IRoninValidatorSet.sol"; +import "../../interfaces/ISlashIndicator.sol"; + +contract MockSlashIndicator is ISlashIndicator { + IRoninValidatorSet public validatorContract; + + modifier onlyCoinbase() { + require(msg.sender == block.coinbase, "SlashIndicator: method caller is not the coinbase"); + _; + } + + constructor(IRoninValidatorSet _validatorSetContract) { + validatorContract = _validatorSetContract; + } + + function slashFelony(address _validatorAddr) external { + validatorContract.slashFelony(_validatorAddr); + } + + function slashMisdemeanor(address _validatorAddr) external { + validatorContract.slashMisdemeanor(_validatorAddr); + } + + function resetCounters(address[] calldata) external {} + + function slash(address _valAddr) external override {} + + function slashDoubleSign(address _valAddr, bytes calldata _evidence) external override {} + + function getSlashIndicator(address _validator) external view override returns (uint256) {} + + function getSlashThresholds() + external + view + override + returns (uint256 misdemeanorThreshold, uint256 felonyThreshold) + {} +} diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index 3012c6872..f476d0b78 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -1,26 +1,24 @@ import { expect } from 'chai'; +import { BigNumber } from 'ethers'; import { ethers, network } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { DPoStaking, - MockSorting, - MockSorting__factory, - RoninValidatorSet, MockRoninValidatorSet, MockRoninValidatorSet__factory, DPoStaking__factory, TransparentUpgradeableProxy__factory, - SlashIndicator, - SlashIndicator__factory, + MockSlashIndicator, + MockSlashIndicator__factory, } from '../../src/types'; -import { BigNumber } from 'ethers'; let roninValidatorSet: MockRoninValidatorSet; let stakingContract: DPoStaking; -let slashIndicator: SlashIndicator; +let slashIndicator: MockSlashIndicator; let coinbase: SignerWithAddress; +let treasury: SignerWithAddress; let deployer: SignerWithAddress; let governanceAdmin: SignerWithAddress; let proxyAdmin: SignerWithAddress; @@ -31,7 +29,7 @@ const slashDoubleSignAmount = 1000; const maxValidatorNumber = 4; const minValidatorBalance = BigNumber.from(2); -const mineTxs = async (fn: () => Promise) => { +const mineBatchTxs = async (fn: () => Promise) => { await network.provider.send('evm_setAutomine', [false]); await fn(); await network.provider.send('evm_mine'); @@ -40,7 +38,7 @@ const mineTxs = async (fn: () => Promise) => { describe('Ronin Validator Set test', () => { before(async () => { - [coinbase, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + [coinbase, treasury, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); validatorCandidates = validatorCandidates.slice(0, 5); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); @@ -48,7 +46,7 @@ describe('Ronin Validator Set test', () => { const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 1 }); const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 3 }); - slashIndicator = await new SlashIndicator__factory(deployer).deploy(roninValidatorSetAddr); + slashIndicator = await new MockSlashIndicator__factory(deployer).deploy(roninValidatorSetAddr); await slashIndicator.deployed(); roninValidatorSet = await new MockRoninValidatorSet__factory(deployer).deploy( @@ -98,7 +96,7 @@ describe('Ronin Validator Set test', () => { }); it('Should be able to wrap up epoch when the epoch is ending', async () => { - await mineTxs(async () => { + await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); @@ -114,7 +112,7 @@ describe('Ronin Validator Set test', () => { }); } - await mineTxs(async () => { + await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); @@ -130,7 +128,7 @@ describe('Ronin Validator Set test', () => { it(`Should be able to wrap up epoch and pick top ${maxValidatorNumber} to be validators`, async () => { await stakingContract .connect(coinbase) - .proposeValidator(coinbase.address, coinbase.address, 1_00 /* 1% */, { value: 100 }); + .proposeValidator(coinbase.address, treasury.address, 1_00 /* 1% */, { value: 100 }); for (let i = 4; i < validatorCandidates.length; i++) { await stakingContract .connect(validatorCandidates[i]) @@ -140,7 +138,7 @@ describe('Ronin Validator Set test', () => { } expect((await stakingContract.getValidatorCandidates()).length).eq(validatorCandidates.length + 1); - await mineTxs(async () => { + await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); @@ -161,6 +159,73 @@ describe('Ronin Validator Set test', () => { }); it('Should be able to submit block reward using coinbase account', async () => { - await roninValidatorSet.connect(coinbase).submitBlockReward(); + await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + }); + + it('Should be able to get right reward at the end of period', async () => { + const balance = await treasury.getBalance(); + await mineBatchTxs(async () => { + await roninValidatorSet.endEpoch(); + await roninValidatorSet.endPeriod(); + await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); + const balanceDiff = (await treasury.getBalance()).sub(balance); + expect(balanceDiff).eq(1); // 100 * 1% + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(99); // remain amount (99%) + }); + + it('Should not allocate minting fee for the slashed validators', async () => { + { + const balance = await treasury.getBalance(); + await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + await slashIndicator.slashMisdemeanor(coinbase.address); + await mineBatchTxs(async () => { + await roninValidatorSet.endEpoch(); + await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); + const balanceDiff = (await treasury.getBalance()).sub(balance); + expect(balanceDiff).eq(0); + // The delegators don't receives the new rewards until the period is ended + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(99); + } + + { + const balance = await treasury.getBalance(); + await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + await mineBatchTxs(async () => { + await roninValidatorSet.endEpoch(); + await roninValidatorSet.endPeriod(); + await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); + const balanceDiff = (await treasury.getBalance()).sub(balance); + expect(balanceDiff).eq(0); + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(99); + } + }); + + it('Should be able to record delegating reward for a successful epoch', async () => { + const balance = await treasury.getBalance(); + await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + await mineBatchTxs(async () => { + await roninValidatorSet.endEpoch(); + await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); + const balanceDiff = (await treasury.getBalance()).sub(balance); + expect(balanceDiff).eq(0); + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(99 * 2); + }); + + it('Should not allocate reward for the slashed validator', async () => { + const balance = await treasury.getBalance(); + await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + await slashIndicator.slashMisdemeanor(coinbase.address); + await mineBatchTxs(async () => { + await roninValidatorSet.endEpoch(); + await roninValidatorSet.endPeriod(); + await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); + const balanceDiff = (await treasury.getBalance()).sub(balance); + expect(balanceDiff).eq(0); + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(99 * 2); }); }); From 9d8ebd944c4c6094c8375f2661a4dbd09d80230f Mon Sep 17 00:00:00 2001 From: nxqbao Date: Wed, 7 Sep 2022 11:24:19 +0700 Subject: [PATCH 080/190] [SlashIndicator] Add gov function. Fix test. Fix typo. --- contracts/interfaces/ISlashIndicator.sol | 21 ++++++-- contracts/slash/SlashIndicator.sol | 64 +++++++++++++++++------- test/slash/SlashIndicator.test.ts | 53 +++++++++++--------- 3 files changed, 93 insertions(+), 45 deletions(-) diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index b5e2ff8f4..eaf83ce85 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -11,7 +11,7 @@ interface ISlashIndicator { enum SlashType { UNKNOWN, - MISDEMAENOR, + MISDEMEANOR, FELONY, DOUBLE_SIGNING } @@ -22,7 +22,7 @@ interface ISlashIndicator { function validatorContract() external view returns (IRoninValidatorSet); /** - * @dev Slashs for inavailability by increasing the counter of validator with `_valAddr`. + * @dev Slashes for unavailability by increasing the counter of validator with `_valAddr`. * If the counter passes the threshold, call the function from the validator contract. * * Requirements: @@ -45,7 +45,7 @@ interface ISlashIndicator { function resetCounters(address[] calldata) external; /** - * @dev Slashs for double signing. + * @dev Slashes for double signing. * * Requirements: * - Only coinbase can call this method @@ -53,13 +53,26 @@ interface ISlashIndicator { */ function slashDoubleSign(address _valAddr, bytes calldata _evidence) external; + /** + * @dev Sets the slash thresholds + * + * Requirements: + * - Only governance admin contract can call this method + */ + function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external; + /** * @dev Gets slash indicator of a validator. */ function getSlashIndicator(address _validator) external view returns (uint256); /** - * @dev Gets slash threshold. + * @dev Gets the slash thresholds. */ function getSlashThresholds() external view returns (uint256 misdemeanorThreshold, uint256 felonyThreshold); + + /** + * @dev Returns the governance admin contract address. + */ + function governanceAdminContract() external view returns (address); } diff --git a/contracts/slash/SlashIndicator.sol b/contracts/slash/SlashIndicator.sol index 10492c8d5..a7bd5c54a 100644 --- a/contracts/slash/SlashIndicator.sol +++ b/contracts/slash/SlashIndicator.sol @@ -14,13 +14,13 @@ contract SlashIndicator is ISlashIndicator { mapping(address => uint256) internal _unavailabilityIndicator; /// @dev The last block that a validator is slashed uint256 public lastSlashedBlock; - /// @dev The threshold to slash when validator is unavailability reaches misdemeanor - uint256 public misdemeanorThreshold; // TODO: add setter by gov admin + uint256 public misdemeanorThreshold; /// @dev The threshold to slash when validator is unavailability reaches felony - uint256 public felonyThreshold; // TODO: add setter by gov admin + uint256 public felonyThreshold; /// @dev The validator contract IRoninValidatorSet public validatorContract; + address internal _governanceAdminContract; modifier onlyCoinbase() { require(msg.sender == block.coinbase, "SlashIndicator: method caller is not the coinbase"); @@ -33,7 +33,10 @@ contract SlashIndicator is ISlashIndicator { } modifier oncePerBlock() { - require(block.number > lastSlashedBlock, "SlashIndicator: cannot slash twice in one block"); + require( + block.number > lastSlashedBlock, + "SlashIndicator: cannot slash a validator twice or slash more than one validator in one block" + ); _; lastSlashedBlock = block.number; } @@ -44,6 +47,10 @@ contract SlashIndicator is ISlashIndicator { validatorContract = _validatorSetContract; } + /////////////////////////////////////////////////////////////////////////////////////// + // SLASHING FUNCTIONS // + /////////////////////////////////////////////////////////////////////////////////////// + /** * @inheritdoc ISlashIndicator */ @@ -60,10 +67,17 @@ contract SlashIndicator is ISlashIndicator { emit ValidatorSlashed(_validatorAddr, SlashType.FELONY); } else if (_count == misdemeanorThreshold) { validatorContract.slashMisdemeanor(_validatorAddr); - emit ValidatorSlashed(_validatorAddr, SlashType.MISDEMAENOR); + emit ValidatorSlashed(_validatorAddr, SlashType.MISDEMEANOR); } } + /** + * @inheritdoc ISlashIndicator + */ + function slashDoubleSign(address _valAddr, bytes calldata _evidence) external override onlyCoinbase { + revert("Not implemented"); + } + /** * @inheritdoc ISlashIndicator */ @@ -71,13 +85,36 @@ contract SlashIndicator is ISlashIndicator { _resetCounters(_validatorAddrs); } + /** + * @dev Resets counter for the validator address. + */ + function _resetCounters(address[] calldata _validatorAddrs) private { + if (_validatorAddrs.length == 0) { + return; + } + + for (uint _i = 0; _i < _validatorAddrs.length; _i++) { + delete _unavailabilityIndicator[_validatorAddrs[_i]]; + } + emit UnavailabilityIndicatorsReset(_validatorAddrs); + } + + /////////////////////////////////////////////////////////////////////////////////////// + // GOVERNANCE FUNCTIONS // + /////////////////////////////////////////////////////////////////////////////////////// + /** * @inheritdoc ISlashIndicator */ - function slashDoubleSign(address _valAddr, bytes calldata _evidence) external override onlyCoinbase { - revert("Not implemented"); + function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external override { + felonyThreshold = _felonyThreshold; + misdemeanorThreshold = _misdemeanorThreshold; } + /////////////////////////////////////////////////////////////////////////////////////// + // QUERY FUNCTIONS // + /////////////////////////////////////////////////////////////////////////////////////// + /** * @inheritdoc ISlashIndicator */ @@ -93,16 +130,9 @@ contract SlashIndicator is ISlashIndicator { } /** - * @dev Resets counter for the validator address. + * @inheritdoc ISlashIndicator */ - function _resetCounters(address[] calldata _validatorAddrs) private { - if (_validatorAddrs.length == 0) { - return; - } - - for (uint _i = 0; _i < _validatorAddrs.length; _i++) { - delete _unavailabilityIndicator[_validatorAddrs[_i]]; - } - emit UnavailabilityIndicatorsReset(_validatorAddrs); + function governanceAdminContract() external view override returns (address) { + return _governanceAdminContract; } } diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index d68d5b0f1..c8fc8bfd7 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -21,33 +21,18 @@ let localIndicators: number[]; enum SlashType { UNKNOWN, - MISDEMAENOR, + MISDEMEANOR, FELONY, DOUBLE_SIGNING, } -const getBlockNumber = async () => { - return network.provider.send('eth_blockNumber'); -}; - const resetCoinbase = async () => { await network.provider.send('hardhat_setCoinbase', [defaultCoinbase]); }; -const validateTwoObjects = async (definedObj: any, resultObj: any) => { - let key: keyof typeof definedObj; - for (key in definedObj) { - const definedVal = definedObj[key]; - const resultVal = resultObj[key]; - - if (Array.isArray(resultVal)) { - for (let i = 0; i < resultVal.length; i++) { - await expect(resultVal[i]).to.eq(definedVal[i]); - } - } else { - await expect(resultVal).to.eq(definedVal); - } - } +const increaseLocalCounterForValidatorAt = async (_index: number, _increase?: number) => { + _increase = _increase ?? 1; + localIndicators[_index] += _increase; }; const setLocalCounterForValidatorAt = async (_index: number, _value: number) => { @@ -129,14 +114,34 @@ describe('Slash indicator test', () => { await network.provider.send('evm_setAutomine', [false]); await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); let tx = doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); + await expect(tx).to.be.revertedWith( + 'SlashIndicator: cannot slash a validator twice or slash more than one validator in one block' + ); await network.provider.send('evm_mine'); await network.provider.send('evm_setAutomine', [true]); - expect(tx).to.be.revertedWith('SlashIndicator: cannot slash twice in one block'); - - await setLocalCounterForValidatorAt(slasheeIdx, 1); + await increaseLocalCounterForValidatorAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); }); + + it('Should not able to slash more than one validator in one block', async () => { + let slasherIdx = 0; + let slasheeIdx1 = 1; + let slasheeIdx2 = 2; + await network.provider.send('evm_setAutomine', [false]); + await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx1]); + let tx = doSlash(coinbases[slasherIdx], coinbases[slasheeIdx2]); + await expect(tx).to.be.revertedWith( + 'SlashIndicator: cannot slash a validator twice or slash more than one validator in one block' + ); + await network.provider.send('evm_mine'); + await network.provider.send('evm_setAutomine', [true]); + + await increaseLocalCounterForValidatorAt(slasheeIdx1); + await validateIndicatorAt(slasheeIdx1); + await setLocalCounterForValidatorAt(slasheeIdx2, 1); + await validateIndicatorAt(slasheeIdx1); + }); }); describe('Slash method: recording and call to validator set', async () => { @@ -164,13 +169,13 @@ describe('Slash indicator test', () => { for (let i = 0; i < misdemeanorThreshold; i++) { tx = await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); } - expect(tx).to.emit(slashContract, 'ValidatorSlashed').withArgs(coinbases[1].address, SlashType.MISDEMAENOR); + expect(tx).to.emit(slashContract, 'ValidatorSlashed').withArgs(coinbases[1].address, SlashType.MISDEMEANOR); await setLocalCounterForValidatorAt(slasheeIdx, misdemeanorThreshold); await validateIndicatorAt(slasheeIdx); }); }); - describe('Reseting counter', async () => { + describe('Resetting counter', async () => { it('Should validator set contract reset counter for one validator', async () => { let tx; let slasherIdx = 0; From c97f8129cd2f90ce3b945207faff046cf5fcd664 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 7 Sep 2022 12:13:38 +0700 Subject: [PATCH 081/190] Add deployment script --- .env.example | 4 + .../mocks/validator/MockSlashIndicator.sol | 4 + contracts/slash/SlashIndicator.sol | 23 +++-- hardhat.config.ts | 12 ++- src/addresses.ts | 9 -- src/config.ts | 93 +++++++++++++++++++ src/deploy/calculate-address.ts | 16 ++++ src/deploy/logic/slash-indicator.ts | 17 ++++ src/deploy/logic/validator-set.ts | 17 ++++ .../dpostaking-proxy.ts} | 10 +- src/deploy/proxy/ronin-validator-proxy.ts | 35 +++++++ src/deploy/proxy/slash-indicator-proxy.ts | 30 ++++++ src/deploy/staking.ts | 29 ------ src/deploy/verify-address.ts | 37 ++++++++ src/script/dpostaking.ts | 20 +--- test/slash/SlashIndicator.test.ts | 13 ++- 16 files changed, 296 insertions(+), 73 deletions(-) delete mode 100644 src/addresses.ts create mode 100644 src/config.ts create mode 100644 src/deploy/calculate-address.ts create mode 100644 src/deploy/logic/slash-indicator.ts create mode 100644 src/deploy/logic/validator-set.ts rename src/deploy/{dpostaking.ts => proxy/dpostaking-proxy.ts} (74%) create mode 100644 src/deploy/proxy/ronin-validator-proxy.ts create mode 100644 src/deploy/proxy/slash-indicator-proxy.ts delete mode 100644 src/deploy/staking.ts create mode 100644 src/deploy/verify-address.ts diff --git a/.env.example b/.env.example index e11050397..88ccb8bf5 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,7 @@ +# Devnet configs +DEVNET_PK=0x0000000000000000000000000000000000000000000000000000000000000000 +DEVNET_URL=http://localhost:8545 + # Testnet configs TESTNET_PK=0x0000000000000000000000000000000000000000000000000000000000000000 TESTNET_URL=https://testnet.skymavis.one/rpc diff --git a/contracts/mocks/validator/MockSlashIndicator.sol b/contracts/mocks/validator/MockSlashIndicator.sol index 5e197a127..7b91087f5 100644 --- a/contracts/mocks/validator/MockSlashIndicator.sol +++ b/contracts/mocks/validator/MockSlashIndicator.sol @@ -39,4 +39,8 @@ contract MockSlashIndicator is ISlashIndicator { override returns (uint256 misdemeanorThreshold, uint256 felonyThreshold) {} + + function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external override {} + + function governanceAdminContract() external view override returns (address) {} } diff --git a/contracts/slash/SlashIndicator.sol b/contracts/slash/SlashIndicator.sol index a7bd5c54a..a7d1f1ee1 100644 --- a/contracts/slash/SlashIndicator.sol +++ b/contracts/slash/SlashIndicator.sol @@ -2,14 +2,12 @@ pragma solidity ^0.8.9; +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "../interfaces/ISlashIndicator.sol"; import "../interfaces/IStaking.sol"; import "../interfaces/IRoninValidatorSet.sol"; -/** - * TODO: Apply proxy pattern to this contract. - */ -contract SlashIndicator is ISlashIndicator { +contract SlashIndicator is ISlashIndicator, Initializable { /// @dev Mapping from validator address => unavailability indicator mapping(address => uint256) internal _unavailabilityIndicator; /// @dev The last block that a validator is slashed @@ -41,9 +39,20 @@ contract SlashIndicator is ISlashIndicator { lastSlashedBlock = block.number; } - constructor(IRoninValidatorSet _validatorSetContract) { - misdemeanorThreshold = 50; - felonyThreshold = 150; + constructor() { + _disableInitializers(); + } + + /** + * @dev Initializes the contract storage. + */ + function initialize( + uint256 _misdemeanorThreshold, + uint256 _felonyThreshold, + IRoninValidatorSet _validatorSetContract + ) external initializer { + misdemeanorThreshold = _misdemeanorThreshold; + felonyThreshold = _felonyThreshold; validatorContract = _validatorSetContract; } diff --git a/hardhat.config.ts b/hardhat.config.ts index eade88eb2..720b80eb8 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -11,7 +11,11 @@ dotenv.config(); const DEFAULT_MNEMONIC = 'title spike pink garlic hamster sorry few damage silver mushroom clever window'; -const { REPORT_GAS, TESTNET_PK, TESTNET_URL, MAINNET_PK, MAINNET_URL } = process.env; +const { REPORT_GAS, DEVNET_PK, DEVNET_URL, TESTNET_PK, TESTNET_URL, MAINNET_PK, MAINNET_URL } = process.env; + +if (!DEVNET_PK) { + console.warn('DEVNET_PK is unset. Using DEFAULT_MNEMONIC'); +} if (!TESTNET_PK) { console.warn('TESTNET_PK is unset. Using DEFAULT_MNEMONIC'); @@ -21,6 +25,11 @@ if (!MAINNET_PK) { console.warn('MAINNET_PK is unset. Using DEFAULT_MNEMONIC'); } +const devnet: NetworkUserConfig = { + url: DEVNET_URL || 'http://localhost:8545', + accounts: DEVNET_PK ? [DEVNET_PK] : { mnemonic: DEFAULT_MNEMONIC }, +}; + const testnet: NetworkUserConfig = { chainId: 2021, url: TESTNET_URL || 'https://testnet.skymavis.one/rpc', @@ -67,6 +76,7 @@ const config: HardhatUserConfig = { accountsBalance: '1000000000000000000000000000', // 1B RON }, }, + 'ronin-devnet': devnet, 'ronin-testnet': testnet, 'ronin-mainnet': mainnet, }, diff --git a/src/addresses.ts b/src/addresses.ts deleted file mode 100644 index c1c5ef797..000000000 --- a/src/addresses.ts +++ /dev/null @@ -1,9 +0,0 @@ -export enum Network { - Hardhat = 'hardhat', - Testnet = 'ronin-testnet', - Mainnet = 'ronin-mainnet', -} - -export type LiteralNetwork = Network | string; - -export const defaultAddress = '0x0000000000000000000000000000000000000000'; diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 000000000..3ee9e1886 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,93 @@ +import { BigNumber, BigNumberish } from 'ethers'; +import { ethers } from 'hardhat'; + +export enum Network { + Hardhat = 'hardhat', + Devnet = 'ronin-devnet', + Testnet = 'ronin-testnet', + Mainnet = 'ronin-mainnet', +} + +export type LiteralNetwork = Network | string; + +export const defaultAddress = '0x0000000000000000000000000000000000000000'; + +export interface InitAddr { + [network: LiteralNetwork]: { + governanceAdmin: string; + validatorContract?: string; + stakingContract?: string; + slashIndicator?: string; + }; +} + +export interface DPoStakingConf { + [network: LiteralNetwork]: + | { + maxValidatorCandidate: BigNumberish; + minValidatorBalance: BigNumberish; + } + | undefined; +} + +export interface SlashIndicatorConf { + [network: LiteralNetwork]: + | { + misdemeanorThreshold: BigNumberish; + felonyThreshold: BigNumberish; + } + | undefined; +} + +export interface RoninValidatorSetConf { + [network: LiteralNetwork]: + | { + maxValidatorNumber: BigNumberish; + numberOfBlocksInEpoch: BigNumberish; + numberOfEpochsInPeriod: BigNumberish; + slashFelonyAmount: BigNumberish; + slashDoubleSignAmount: BigNumberish; + } + | undefined; +} + +export const initAddress: InitAddr = { + [Network.Devnet]: { + governanceAdmin: '0x93b8eed0a1e082ae2f478fd7f8c14b1fc0261bb1', + }, +}; + +// TODO: update config for devnet, testnet & mainnet +export const stakingConfig: DPoStakingConf = { + [Network.Hardhat]: undefined, + [Network.Devnet]: { + minValidatorBalance: BigNumber.from(10).pow(18).mul(1000), // 1000 RON + maxValidatorCandidate: 100, + }, + [Network.Testnet]: undefined, + [Network.Mainnet]: undefined, +}; + +// TODO: update config for devnet, testnet & mainnet +export const slashIndicatorConf: SlashIndicatorConf = { + [Network.Hardhat]: undefined, + [Network.Devnet]: { + misdemeanorThreshold: 50, + felonyThreshold: 150, + }, + [Network.Testnet]: undefined, + [Network.Mainnet]: undefined, +}; +// TODO: update config for devnet, testnet & mainnet +export const roninValidatorSetConf: RoninValidatorSetConf = { + [Network.Hardhat]: undefined, + [Network.Devnet]: { + maxValidatorNumber: 21, + numberOfBlocksInEpoch: 600, + numberOfEpochsInPeriod: 48, // 1 day + slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), // 10 RON + slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), // 10 RON + }, + [Network.Testnet]: undefined, + [Network.Mainnet]: undefined, +}; diff --git a/src/deploy/calculate-address.ts b/src/deploy/calculate-address.ts new file mode 100644 index 000000000..6a9da77f5 --- /dev/null +++ b/src/deploy/calculate-address.ts @@ -0,0 +1,16 @@ +import { ethers, network } from 'hardhat'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { initAddress } from '../config'; + +const deploy = async ({ getNamedAccounts }: HardhatRuntimeEnvironment) => { + const { deployer } = await getNamedAccounts(); + let nonce = await ethers.provider.getTransactionCount(deployer); + initAddress[network.name].slashIndicator = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++ }); + initAddress[network.name].stakingContract = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++ }); + initAddress[network.name].validatorContract = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++ }); +}; + +deploy.tags = ['CalculateAddresses']; +deploy.dependencies = ['ProxyAdmin', 'DPoStakingLogic', 'SlashIndicatorLogic', 'RoninValidatorSetLogic']; + +export default deploy; diff --git a/src/deploy/logic/slash-indicator.ts b/src/deploy/logic/slash-indicator.ts new file mode 100644 index 000000000..2c1890bd5 --- /dev/null +++ b/src/deploy/logic/slash-indicator.ts @@ -0,0 +1,17 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + await deploy('SlashIndicatorLogic', { + contract: 'SlashIndicator', + from: deployer, + log: true, + }); +}; + +deploy.tags = ['SlashIndicatorLogic']; +deploy.dependencies = ['ProxyAdmin']; + +export default deploy; diff --git a/src/deploy/logic/validator-set.ts b/src/deploy/logic/validator-set.ts new file mode 100644 index 000000000..dc66019ae --- /dev/null +++ b/src/deploy/logic/validator-set.ts @@ -0,0 +1,17 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + await deploy('RoninValidatorSetLogic', { + contract: 'RoninValidatorSet', + from: deployer, + log: true, + }); +}; + +deploy.tags = ['RoninValidatorSetLogic']; +deploy.dependencies = ['ProxyAdmin']; + +export default deploy; diff --git a/src/deploy/dpostaking.ts b/src/deploy/proxy/dpostaking-proxy.ts similarity index 74% rename from src/deploy/dpostaking.ts rename to src/deploy/proxy/dpostaking-proxy.ts index 3edcfe9b1..59bfb4a20 100644 --- a/src/deploy/dpostaking.ts +++ b/src/deploy/proxy/dpostaking-proxy.ts @@ -1,7 +1,7 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { stakingConfig } from '../script/dpostaking'; -import { DPoStaking__factory } from '../types'; +import { stakingConfig, initAddress } from '../../config'; +import { DPoStaking__factory } from '../../types'; const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { const { deploy } = deployments; @@ -11,8 +11,8 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const logicContract = await deployments.get('DPoStakingLogic'); const data = new DPoStaking__factory().interface.encodeFunctionData('initialize', [ - stakingConfig[network.name]!.validatorContract, - stakingConfig[network.name]!.governanceAdminContract, + initAddress[network.name]!.validatorContract, + initAddress[network.name]!.governanceAdmin, stakingConfig[network.name]!.maxValidatorCandidate, stakingConfig[network.name]!.minValidatorBalance, ]); @@ -26,6 +26,6 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme }; deploy.tags = ['DPoStakingProxy']; -deploy.dependencies = ['ProxyAdmin', 'DPoStakingLogic']; +deploy.dependencies = ['ProxyAdmin', 'DPoStakingLogic', 'SlashIndicatorProxy']; export default deploy; diff --git a/src/deploy/proxy/ronin-validator-proxy.ts b/src/deploy/proxy/ronin-validator-proxy.ts new file mode 100644 index 000000000..377a4b7e0 --- /dev/null +++ b/src/deploy/proxy/ronin-validator-proxy.ts @@ -0,0 +1,35 @@ +import { network } from 'hardhat'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { roninValidatorSetConf, initAddress } from '../../config'; +import { RoninValidatorSet__factory } from '../../types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + const proxyAdmin = await deployments.get('ProxyAdmin'); + const logicContract = await deployments.get('RoninValidatorSetLogic'); + + const data = new RoninValidatorSet__factory().interface.encodeFunctionData('initialize', [ + initAddress[network.name]!.governanceAdmin, + initAddress[network.name]!.slashIndicator, + initAddress[network.name]!.stakingContract, + roninValidatorSetConf[network.name]!.maxValidatorNumber, + roninValidatorSetConf[network.name]!.numberOfBlocksInEpoch, + roninValidatorSetConf[network.name]!.numberOfEpochsInPeriod, + roninValidatorSetConf[network.name]!.slashFelonyAmount, + roninValidatorSetConf[network.name]!.slashDoubleSignAmount, + ]); + + await deploy('RoninValidatorSetProxy', { + contract: 'TransparentUpgradeableProxy', + from: deployer, + log: true, + args: [logicContract.address, proxyAdmin.address, data], + }); +}; + +deploy.tags = ['RoninValidatorSetProxy']; +deploy.dependencies = ['ProxyAdmin', 'RoninValidatorSetLogic']; + +export default deploy; diff --git a/src/deploy/proxy/slash-indicator-proxy.ts b/src/deploy/proxy/slash-indicator-proxy.ts new file mode 100644 index 000000000..5e25c31eb --- /dev/null +++ b/src/deploy/proxy/slash-indicator-proxy.ts @@ -0,0 +1,30 @@ +import { network } from 'hardhat'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { slashIndicatorConf, initAddress } from '../../config'; +import { SlashIndicator__factory } from '../../types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + const proxyAdmin = await deployments.get('ProxyAdmin'); + const logicContract = await deployments.get('SlashIndicatorLogic'); + + const data = new SlashIndicator__factory().interface.encodeFunctionData('initialize', [ + slashIndicatorConf[network.name]!.misdemeanorThreshold, + slashIndicatorConf[network.name]!.felonyThreshold, + initAddress[network.name]!.validatorContract, + ]); + + await deploy('SlashIndicatorProxy', { + contract: 'TransparentUpgradeableProxy', + from: deployer, + log: true, + args: [logicContract.address, proxyAdmin.address, data], + }); +}; + +deploy.tags = ['SlashIndicatorProxy']; +deploy.dependencies = ['ProxyAdmin', 'SlashIndicatorLogic']; + +export default deploy; diff --git a/src/deploy/staking.ts b/src/deploy/staking.ts deleted file mode 100644 index 5d9677f4f..000000000 --- a/src/deploy/staking.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { HardhatRuntimeEnvironment } from 'hardhat/types'; - -import { Staking__factory } from '../types'; - -const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { - const { deploy } = deployments; - let { deployer } = await getNamedAccounts(); - - const proxyAdmin = await deployments.get('ProxyAdmin'); - const stakingLogic = await deploy('StakingLogic', { - contract: 'Staking', - from: deployer, - log: true, - }); - - const data = new Staking__factory().interface.encodeFunctionData('initialize', []); - - await deploy('StakingProxy', { - contract: 'TransparentUpgradeableProxy', - from: deployer, - log: true, - args: [stakingLogic.address, proxyAdmin.address, data], - }); -}; - -deploy.tags = ['StakingContract']; -deploy.dependencies = ['ProxyAdmin', 'SortingLibrary']; - -export default deploy; diff --git a/src/deploy/verify-address.ts b/src/deploy/verify-address.ts new file mode 100644 index 000000000..3bceec12a --- /dev/null +++ b/src/deploy/verify-address.ts @@ -0,0 +1,37 @@ +import { ethers, network } from 'hardhat'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { initAddress } from '../config'; + +const deploy = async ({ deployments }: HardhatRuntimeEnvironment) => { + const indicator = await deployments.get('SlashIndicatorProxy'); + const stakingContract = await deployments.get('DPoStakingProxy'); + const validatorContract = await deployments.get('RoninValidatorSetProxy'); + + if (initAddress[network.name].slashIndicator?.toLowerCase() != indicator.address.toLowerCase()) { + throw Error( + `invalid address for indicator, expected=${initAddress[ + network.name + ].slashIndicator?.toLowerCase()}, actual=${indicator.address.toLowerCase()}` + ); + } + if (initAddress[network.name].stakingContract?.toLowerCase() != stakingContract.address.toLowerCase()) { + throw Error( + `invalid address for stakingContract, expected=${initAddress[ + network.name + ].stakingContract?.toLowerCase()}, actual=${stakingContract.address.toLowerCase()}` + ); + } + if (initAddress[network.name].validatorContract?.toLowerCase() != validatorContract.address.toLowerCase()) { + throw Error( + `invalid address for validatorContract, expected=${initAddress[ + network.name + ].validatorContract?.toLowerCase()}, actual=${validatorContract.address.toLowerCase()}` + ); + } + console.log('All checks are done'); +}; + +deploy.tags = ['VerifyAddress']; +deploy.dependencies = ['ProxyAdmin', 'DPoStakingProxy', 'SlashIndicatorProxy', 'RoninValidatorSetProxy']; + +export default deploy; diff --git a/src/script/dpostaking.ts b/src/script/dpostaking.ts index ec6db4c0a..4a6026238 100644 --- a/src/script/dpostaking.ts +++ b/src/script/dpostaking.ts @@ -1,20 +1,2 @@ import { BigNumberish } from 'ethers'; -import { LiteralNetwork, Network } from '../addresses'; - -interface DPoStakingConf { - [network: LiteralNetwork]: - | { - validatorContract: BigNumberish; - governanceAdminContract: BigNumberish; - maxValidatorCandidate: BigNumberish; - minValidatorBalance: BigNumberish; - } - | undefined; -} - -// TODO: update config for testnet & mainnet -export const stakingConfig: DPoStakingConf = { - [Network.Hardhat]: undefined, - [Network.Testnet]: undefined, - [Network.Mainnet]: undefined, -}; +import { LiteralNetwork, Network } from '../config'; diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index c8fc8bfd7..434b7fa48 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -7,12 +7,14 @@ import { SlashIndicator__factory, MockEmptyValidatorSet, MockEmptyValidatorSet__factory, + TransparentUpgradeableProxy__factory, } from '../../src/types'; import { Address } from 'hardhat-deploy/dist/types'; let slashContract: SlashIndicator; let deployer: SignerWithAddress; +let proxyAdmin: SignerWithAddress; let dummyValidatorsContract: MockEmptyValidatorSet; let vagabond: SignerWithAddress; let coinbases: SignerWithAddress[]; @@ -56,12 +58,18 @@ describe('Slash indicator test', () => { let misdemeanorThreshold: number; before(async () => { - [deployer, vagabond, ...coinbases] = await ethers.getSigners(); + [deployer, proxyAdmin, vagabond, ...coinbases] = await ethers.getSigners(); localIndicators = Array(coinbases.length).fill(0); dummyValidatorsContract = await new MockEmptyValidatorSet__factory(deployer).deploy(); - slashContract = await new SlashIndicator__factory(deployer).deploy(dummyValidatorsContract.address); + const logicContract = await new SlashIndicator__factory(deployer).deploy(); + const proxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + logicContract.address, + proxyAdmin.address, + logicContract.interface.encodeFunctionData('initialize', [50, 150, dummyValidatorsContract.address]) + ); + slashContract = SlashIndicator__factory.connect(proxyContract.address, deployer); await dummyValidatorsContract.connect(deployer).setSlashingContract(slashContract.address); let thresholds = await slashContract.getSlashThresholds(); @@ -69,7 +77,6 @@ describe('Slash indicator test', () => { misdemeanorThreshold = thresholds[1].toNumber(); defaultCoinbase = await network.provider.send('eth_coinbase'); - console.log('Default coinbase:', defaultCoinbase); }); describe('Single flow test', async () => { From 91f3fdb3a239c5b3ac5861eeb0c83dda82951689 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 7 Sep 2022 15:09:53 +0700 Subject: [PATCH 082/190] Fix comments --- contracts/interfaces/IStaking.sol | 2 +- contracts/ronin-validator/RoninValidatorSet.sol | 4 ++-- contracts/staking/Staking.sol | 2 +- contracts/staking/StakingManager.sol | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 6910787d7..60c33bd01 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -287,7 +287,7 @@ interface IStaking { function claimRewards(address[] calldata _consensusAddrList) external returns (uint256 _amount); /** - * @dev Claims all pending rewards and delegates them to the consensus address. + * @dev Claims the rewards and delegates them to the consensus address. * * Requirements: * - The method caller is not the candidate admin. diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index 521645b0e..12c223689 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -27,9 +27,9 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { /// @dev The maximum number of validator. uint256 internal _maxValidatorNumber; - /// @dev Returns the number of epochs in a period - uint256 internal _numberOfBlocksInEpoch; /// @dev The number of blocks in a epoch + uint256 internal _numberOfBlocksInEpoch; + /// @dev Returns the number of epochs in a period uint256 internal _numberOfEpochsInPeriod; /// @dev The last updated block uint256 internal _lastUpdatedBlock; diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 3f252c5c4..1b3b78cc4 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -289,7 +289,7 @@ contract Staking is IStaking, Initializable { } /** - * @dev Claims all pending rewards and delegates them to the consensus address. + * @dev Claims the rewards and delegates them to the consensus address. */ function delegateRewards(address[] calldata _consensusAddrList, address _consensusAddrDst) external diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index af1ecd2d9..330b67a57 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -311,7 +311,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { } /** - * @dev Claims all pending rewards and delegates them to the consensus address. + * @dev Claims the rewards and delegates them to the consensus address. */ function _delegateRewards( address _user, From 94ecac460ee94ee93c0578893115c63dc87ff66f Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 7 Sep 2022 17:27:52 +0700 Subject: [PATCH 083/190] Fix visibility --- contracts/interfaces/IRoninValidatorSet.sol | 10 ++++++++++ contracts/interfaces/ISlashIndicator.sol | 2 ++ contracts/staking/RewardCalculation.sol | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index aa854bad0..de1092541 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -87,6 +87,16 @@ interface IRoninValidatorSet { */ function noPendingReward(address[] memory, uint256 _period) external view returns (bool[] memory); + /** + * @dev The amount of RON to slash felony. + */ + function slashFelonyAmount() external view returns (uint256); + + /** + * @dev The amount of RON to slash felony. + */ + function slashDoubleSignAmount() external view returns (uint256); + /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR NORMAL USER // /////////////////////////////////////////////////////////////////////////////////////// diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index eaf83ce85..b99c81df5 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -6,6 +6,7 @@ import "../interfaces/IRoninValidatorSet.sol"; interface ISlashIndicator { // TODO: fill comment for event. IE: Emitted when... + // TODO: add event for thresholds event ValidatorSlashed(address indexed validator, SlashType slashType); event UnavailabilityIndicatorsReset(address[] validators); @@ -58,6 +59,7 @@ interface ISlashIndicator { * * Requirements: * - Only governance admin contract can call this method + * */ function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external; diff --git a/contracts/staking/RewardCalculation.sol b/contracts/staking/RewardCalculation.sol index 959826324..45b855c4d 100644 --- a/contracts/staking/RewardCalculation.sol +++ b/contracts/staking/RewardCalculation.sol @@ -173,7 +173,7 @@ abstract contract RewardCalculation { * @notice This method should be called before transferring rewards for the user. * */ - function _claimReward(address _poolAddr, address _user) public returns (uint256 _amount) { + function _claimReward(address _poolAddr, address _user) internal returns (uint256 _amount) { _amount = getClaimableReward(_poolAddr, _user); emit RewardClaimed(_poolAddr, _user, _amount); SettledPool memory _sPool = _settledPool[_poolAddr]; From 1dae65dd2ba96a0c6107fb54830012552ce3b2ef Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 7 Sep 2022 17:32:21 +0700 Subject: [PATCH 084/190] Update README.md --- README.md | 87 +++++++++++++++++- assets/Validator Contract Overview.drawio.png | Bin 0 -> 98326 bytes 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 assets/Validator Contract Overview.drawio.png diff --git a/README.md b/README.md index 1bd6e89ee..857f00cd0 100644 --- a/README.md +++ b/README.md @@ -1 +1,86 @@ -# Ronin DPos Contracts \ No newline at end of file +# Ronin DPoS Contracts + +The next version of smart contracts that power Ronin DPoS network. + +_NOTE: This smart contract version does not includes method to prevent the 51% attack. It is not finalized at the implementation time._ + +## Staking contract + +An user can propose themselves to be a validator candidate by staking their RON. Other users are allowed to register as delegators by staking any amount of RON to the staking contract, he/she can choose a validator to stake their coins. + +The ones on top `N` users with the highest amount of staked coins will become validators. + +### Validator Candidate + +**Proposing validator** + +| Params | Explanation | +| ------------------------ | -------------------------------------------------------------------------------------------- | +| `uint256 commissionRate` | The rate to share for the validator. Values in range [0; 100_00] stands for [0; 100%] | +| `address consensusAddr` | Address to produce block | +| `address treasuryAddr` | Address to receive block reward | +| `msg.value` | The amount of RON to stake, require to larger than the minimum RON threshold to be validator | + +The validator candidates can deposit or withdraw their funds afterwards as long as the staking balance must be greater than the minimum RON threshold. + +**Renounce validator** + +The candidates can renounce the validator propose and take back their deposited RON. + +### Delegator + +The delegator can choose the validator to stake and receive the commission reward: + +| Methods | Explanation | +| --------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| `delegate(consensusAddr)` | Stakes `msg.value` amount of RON for a validator `consensusAddr` | +| `undelegate(consensusAddr, amount)` | Unstakes from a validator | +| `redelegate(consensusAddrSrc, consensusAddrDst, amount)` | Unstakes `amount` RON from the `consensusAddrSrc` and stake for `consensusAddrDst` | +| `getRewards()` | Returns the pending rewards and the claimable rewards | +| `claimRewards(consensusAddrList)` | Claims all the reward from the validators | +| `delegatePendingReward(consensusAddrList, consensusAddr)` | Claims all the reward and delegates them to the consensus address | + +### Reward Calculation + +- Read how the reward is calculated for delegator at [Staking Problem: Reward Calculation](https://www.notion.so/skymavis/Staking-Problem-Reward-Calculation-bd47bbcefde24bbd8e959bee45dfd4a5). +- See [`RewardCalculation` contract](./contracts/staking/RewardCalculation.sol) for the implementation. + +## Validator Contract + +The validator contract collects and distributes staking rewards, syncs the validator set from the staking contract at the end of every epoch. + +![image](./assets/Validator%20Contract%20Overview.drawio.png) +_Validator contract flow overview_ + +1. The block producer trigger to the Validator contract to wrap up the epoch. +2. Validator contract counts the validator/delegator rewards to the Staking contract. +3. Validator contract syncs validator set from staking contract. + +At the end of period, the validator contract: + +4. Transfers the rewards for validators. +5. Resets the slashing counter. + +## Slashing + +The validators will be slashed when they do not provide the good service for Ronin network. + +**Unavailability** + +- If a validator missed >= `misdemeanorThreshold` blocks in a day: Cannot claim the reward on that day + +- If a validator missed >= `felonyThreshold` blocks in a day: + - Cannot claim the reward on that day. + - Be slashed `slashFelonyAmount` amount of self-delegated RON. + - Be put in jail for `felonyThreshold` days. + +**Double Sign** + +- If a validator submit more than 1 block at the same `block.number`: + - Cannot claim the reward. + - Be put in jail for `type(uint256).max` days. + - Be slashed `slashDoubleSignAmount` amount of self-delegated RON. + +## Contract Interaction flow + +Read the contract interaction flow at [DPoS Contract: Interaction Flow](https://www.notion.so/skymavis/DPoS-Contract-Interaction-Flow-3a535cf9048f46f69dd9a45958ad9b85). diff --git a/assets/Validator Contract Overview.drawio.png b/assets/Validator Contract Overview.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..a9e6ea745761d8e8cb6d14873c1595e3024676e5 GIT binary patch literal 98326 zcmeFZ2{@GP`#-FB$P+Cp6^S%L)-jkt_GLEvP9lS0Y=arw*wbPuTV*L*WeK4|O17S; zNR&N7k%W-!`+MEw`Bl&N`SpK~<2~N@fBfIy(GlaG`@XO1I`{K)o#z#JAx5J}E2n;Aub^yA}%8$ zjx>ae^C6VQ;oyf9N)#o7GT*r0+TPCf`wOK+#lZ^%w5)ANu2eF`fe)bq{>Hje?VQ1H za2fp6F#tbI!GE~86+DF{9)ySX{rnb;BVBrpdJDFjj!Aq_4I;4y|;ntT$f;IlKy z$qxKN+u1mgp+{65JjgEKiUwRT1!%0R}!vE zF>^*48e(kBea($^;1mME+<m$#vhro9~+uWY7<^3m|oRnt;dwV?o#tJv6(?8$0g zdU$V=yS6ZzFMn_{J$bio%ITF@HI^i06rDqb?WW|C4UUwtCURfFg#<4x6&B9I(#lD=l@5-2HC z4}^@eEsz-%97O~C)b;W~84#sxOzf~!TeKQNO<6}mS4zW4T2CCO>S;jMB>~ZvGNd?( ztJ2Um8Xg*^fYK^PNUVc{x&s!BEsen&iet^Ju~eLyw~@H35e4Z<_Rx^>!uhHZ;3z{c zO${PK6751!^DzbTr(s6WL~E-_c&m|IbkxldcE&1BBxN&WV+m^%G%F`P64gOZO;y`L z)d1y4!D@R-=-3-;Nb9*7Y5_6SA$XECt+hx}=9-cY8iwljR2e-l6=Q<9mVuM1i@K@4 zgB}=z09VuTQb8E{sAvIw(jc1~lDvps6lYC4TYU;e%g7jMEa9So!y3UI%vHs;J&^8goq~Y0QYe+@g!*JW2rU-v?*Rj zLLBeusES59xJ$XIsd#(Z>uNZ_k@^@1XK}2yxwIA<>4P=|%Tl#f)AO-2L(uH0c4}tc zXsWv*#=}e<7z_<3NndNUI#Sn_2A46k^)Mmf^oZ_y5WiJ5Y2K#h?r>9Q9d!*~s=bSp zgNvaiTEYR2Ah~HdN)l0OrX&NPR2l?=wwtxIuOm@c+`wB>mu%~zj8ijkmhm-pu|pwj zaFPZDsvg8{X=@v@jkdkMxiye86&F=)H;l2NswzR<2d?k#LMBO=t5ckPTs`%@Fchqw zH%VFCR@zPnC3IUh}1U5p9YOj1VG)><8>X>aR@#A7hhS`-q(TfhEeD#ev%L=_GYBtD1GI;XlaUJ+sY=5jiDnWwa9NsS zjJ5^8_3@HY#@5zYNfVNZ4Z_UVRtIgQPH@q3#Gs6cK3F(hLs?J52BQq7qG4q11tw(b zV`J>#se?lJddax@kTl_L8Yo{WioT78uA!Qfo3Dhgs|mu{lOU-=#Y>Xi4Sd0qu4o6W zl%utsjwk9N7qP?WMk(+@B*4fa*8Kvw)bJVcLI?)X6B=jU)^wg|r(g=z# z6;DGMI-tGaX5MNj0@2-sAdRyT*Vop;`kHB|QDsO@u5fiz2@g{rJ*>6~&A>y7h(?=g zh!brorbt&cshNqOT8yHvIh2|tpHb=Taf2d=u9dTM@VB%Hmv>Z`VM1r#q zk?Nr?J!7PijT9hua~sFt}ezv+gID&4kP92i4m95aVI%Ssi0iQ;%buGHs&~>>L_=z z3&O_P)7uq=$CydGONiT=8=}!9tUF%E&Ku<|ZQvsb?1Uqc;I8K7s^hNb0T>{qBSpjd zNc$L5e6`?YM^~&GLDxxFlT1cSdLuj$L?Ttv-A>g`!qJ>S(nWdNc%x-p)SNKt5@?h! zmZ&V{X{4gzNd%Key1U5eU?nA73?96#2exzF%kwCABrT-&dy%k&Bs$$!^csJf{-$zxM=yPNnwnr zb~@f9H6q%RKr{x-Hutc_86#XZtUZv4t%Sa$ zp{s@$%?532C=NHVb#uWGj8G_LxG~DngXp2>L2)uvMk?#0F#7stXlGMX2^%cZkW92U z#Q7kJ9&mS0sxSsR#;RZV>1NHukDikFc+0i)vN2$lg?cEr&njmauh8COF|Z~-N$s_Kf@ z*3#1;Xt`);d25^N7!nPzNF6ONErh$9hNhj7jGD0t#?yo#j-si!s%X=+@HQSk_6V}H zBNFN9P6Y(hFqU*xp=62@J{Q<;R-rl`3(YkO*YE30{WSbHd|I7>*% zkST`V2y1hauBx^j4NZZIqmjyx=fjvdyE;*%4bk2@5TD8`dEJHVJ;6a=s~TjXYublp1C{o8;|dm)SEHr zx)W!w_&V_L*3HrV37y_iQgIhX^w$ho5hcQm#zj9;q2_EH}JOAy)TWgsyF?;uW zupaw2p1?5E|Mp^~bkr8+xPFy_C;zrGz-)zod-1maU6_49J%s77505+$?o(1OH_ox+ zCY#c^qECFol#LcVZa%YJG%?=XG3TJs#1kfNP4^1*{ih^F?pAL_l*RA=Rn%??@+eDO zb8kBiNs)kNdIGFE&uV3a`bw&UCHeI$WM8uJNtcpb%bsI?#~jth+*f8gvZXpKDDS&+ zRem9&QXnAiGF+E_T_DA6LM+)}qxq$liz6mgvI7!J0U>WOG5!ObeHf?w663185!x>= zsNS{>6LV2%msH5Q(6=(j8EDJ!x!iric=?QQ<$OOW$u9mNsyOf%rcq((%ifn!F2!#& zh6Qze>#IwQ&i$fgA~(VG!o`BQ!l0GBQF$C8Ejnm< zfLkz5wCB)0YJ0_8@AcHLXZahv7uA^KEJs?jxRO;j#d1?FwgKs0*|>NyxSF~Lwlw~j zTsGAx*J6EvE534NjGT?RYvEqIukB7GEApLqz{jK9SRKEvmQQ;kC{c3b52t%30!wi| zH4E^Q1FGDMy0pcXG=;te#pMq>Tm1T#dZ%X!gDPYdqc9&21(a|(HYir!BkMYk@00D8F@dX7Y1u<3hGd3KsutV)B_Oi2cvnuPz_I7&s@7JF z`fn)oC(91+UgP4duKHpp=Cra31P#;eT=eSfNa;Hdyywa+L)}!`+H$VtK=i{#-=P$w z9fQ+OzZgyIAj8i#O7{*hUH5KuxIvc>@P)`8*;OQIxlnJS-C6v{ehcyZs?|loRW5W2 zPxxBYN7aVUre1ID@{$gByFPZgIvsH6XFkO2a)!;M4tM=3*KLETAcRA)6RYhkO?~whR-u~#Gg>gj_7DhgX-&z|9!;}p(Hy+uP z-3@JgZBxPfY@RO~4-ISC-)AI*IsM zgR6!A#Kbi47U0)SR!9~Dv5cuMTXAK$7>L0ulb-!m^R>gzILVsN4^cEr&bE8J6fsGe zx}dmxM5L%eVk)Hy*upFs$|u8@loY@5N5f`KG~>lFa?r^!Gmo!Nm~`sjR5D>g|Nqh!sW4 zl@XI{jDC%KU%;G^U-U&qUbx5V!caDs_d9qko4IF2S<=j#RG#nv^fPWYnB5F1pYLae z6o~*U-2GR9&bnFQ*p)~+1Cv(kqcq76<&os+Ki(;7#1;C!a%wh6TWlRk*2bT%Z5o&h z0y>-bs&}W%$C!3DOTV!a6WPHl2G1kvgN&z5d}kaFDZNGD%+G?mR}{o9 zK1{Fu*R_x}BAPuGq~_Ua;WCDc1sHK=SCY3+JZ(VHP3$jzBWE}wkXmIJA67qvyJWv0U_jW95G zl3Y=;xUhxkP)wA<;^*lr*(dk{Rz|G~iD5O9U!U&G?hJwvubr4mYPu75BvSZ3MoJYH z|0&|AG_B7uFF$uJDgfJVW}k9=@;;g@XUkNa-# zf%UZZPeup%FM{P8r*r{Eu2}&W)_nh^=t(|lLe3qO>%$M{r<)Thix(#_$u$!bz>he7 zLc{dH5aGvTzdhw%y(>HPrKm}}+okkW-tpMIkLLR$^VY1$6*J9$Z5K-}s9YQwSsUO$ z_D~HX4jyBJ^{6$;{A#(YK(x5XH!Qn;o#&R47;{|W$|TT;oLrX_9-02Iqs<5Hll5MG zz8#I4m~PS0Iq7__HZi^jSYp!BSgB6ODes=dH1z!eV7G729w#XtrhUFKojO;}59HL{ zCb04uhf~I79fe0%{lghvMp{nage1*bJ?_)vz7fY5N@ zp!%=zMCfrK$+u(m#MUkT>tLl6o=0Eqzid8s-C3UxLzsxQKQVBS)MjL&1=(x4_WoG( zVJsLPy$C3r%)90Ow|b%Mea5CoYQLS@!!_3z6twszI`GQ)bQKjXV_%!4%K*B$_Km4>F?T2_lj(j85_%BHnFaptO z>kB93hB@yJ&9EeF|MS<;()WT`N1r!8Wt|!6q1Ae5E+d!DN?<{(;pqJEjxIl3|$0d=Aof5F(1e1D|Zden8^M(vIn!z;Re1! zi|>zCn{GjXfw%pCHE`JCcK59QefW(ze`ie6ABO?W8l-eje4lx`GepnH1fIHI7R?d{ z+;K32iqwWT2_ZmaJlA_=MDuIJk5*TSxTPsHi9X$DbJ$Vz%v{3tP7f$K-r0uLaT2EnlJ6y2Jh5yQo=&8sr{-)!GQM>g%KkL3rhx zPw2zGu**J_+GpMuL4I}j(N?Am*d$ksQUgH>H@|8zvclP;;cq|r>0mHEa)OS*nmv!F zvMB8OLUW~+-HvME{Z=@C`y}@aQ8DE>!DPN2udjTg+cmbgPZMdpIn3@p&9AKcP7?g_ zzH|bf!AAa^w5b=S7q>jTIM2FQq>`1#URcE+(T~(Eo|g_z&e_&iDDebo=qxzt5=MbV z#PZbbeMSq;I=i!V_H4*3am202;?C-cm(>Tw`V`)OUVU{S%uKkfW~nqotmaL-e%Hw= zE7F8;rL}E2HD^*}aOcGuD3cBtorylKU*Y42n;U>?3vVM z7Xq_)=G%d}`Yo9M$d%zN$Ge?z<5tXZjB>_VH*q6PGfNb~ohLF&I_Ukgkg?|@M5HP+ z{e#&1&bwx4)`pRf&7Y6h*LOZ0vCqH}EYngzq%u>U+m6BBzt1Z33DQE!izs_8FgHT&~mX$qE5gFFwrpE?j4Ye020F=JMl)5-odS#Xt-1L>P`E+dNGm|qJ zPqf=l>tzO1d8|oyK9bnWS9$O?r||vO`Nh?Dr}>>83NXi=e{vxM1%?Va?yi)3h3N$I z4Nas`Xl*=oEq<16V%6oV1|SH@Dh%&f$TSbWQmu{W=+m!Mj3lcf^?#9EsY*6jlB@-N zZTz%X|j4szY~_`^*YhJ&$5*CKq547fKlL>3&Ip-cO~ zn(>4ef2oiG8=7+G$xYl+;NYsuU5cFm&L5GEG_}o{oLPAUmb0Al5@Mca#upD<<76*tN&gyfGBuAWQ-6QUbwxbF;-CJ z49q|P&v9DnePYgKDbqWv?|8hPs$*j$SyFF2WSi*IKkPHo)TiGyx<%nBCni)q9^w?E zoLQ*))!CK$mG09+^}1K)iAvj$FtNA7p2JG&`$C%fp&mr9=i z2>_frBAQci_&ov$w9)0pr zgR?-=bmJwf9joC6{7!dGR~{*UIUfm<$O9)--T8W}GEaPMO{>Z*28wbX8N(PU3~`Fp z>po-T@v)_=VDQGPHv}eM1@}T?(=j&$mb0;B1z0UM#`aIcaC-gB6YsIx?8Gk%#lNZ1OQH{Sm^u7SKEWHjz4IfkN>t}=FeFcE3xlrWzu`j4cF%g%6uH0s0%E0+nU*1(u zHIOoO#?FyRJHL+iF_JSh0jJJA%z!indXHS!nG@6Dt8}afJc<^!>pIp~+Q|!I13LWsyus?8-wVAx#4& z?Jf{~LsDPiyDC<%0v7dfGUN2R*iPg?~j8t1$?D@FY{UHm5HYQ?$}nuChEcxq@O%5&Ro+U()aYCuC67>-q^lH@c;I<+@DF<-Jz@kY73(bSQqQ zv~Oo|g`iZBS5=o#<*Vlf*V({4#!Q07pLy+S+})=f_Scs-Wqj-E)pR{Eroqrn(n^tyId1V1|7r!mRI&lw#^PVR23Ku|o!gqWN#&ysAlkSXYDd-ivDL8-dKYimg{EsHzglDv3xA$6DXj`I))dP1 zYQc%W@+teh-Vve73SZXlxBYU?yGQdY5i2LYt>hvn4%y>lKI(EWOI(muZeD$6V9*C} zTY5!5*of@qEjdpnjHf!>WXJaPeF!g}-=TAdmOo78rh8Xafg6&ms#!8QWBYAY`p(>h zCbz>>&$$*M-!gFTurG2Yw^<=5yF2ZA6uzo{PhTK*$0yGkZ}EZVLi2EP6^v<0wgsAi zqxitv9Ah@v(>-~|oMf*s$7z#Kgh&e)pQgYkgJ8I5 z!aq5dHppX%^=E<~U&%8lOhZ>%%`{c!2muemCvP#xVsys#CNlM!zbP>q6EiI>ezY=l zEvxwzUEY%S*MZKQ^F0c5VH0l8O1qnG=|vVY)OD`P9-kbW3Gwoo=<=1Ccu>(VmmDiP zHdWfadLsEva#ef3-0|2i?{-Y?>)Z8pK`wbEy}Rk?Bv<2nds7vk{LUTZjBEJ|x-d)@ zZGic{^)>nCv0%fZQ`TPi>&|qi{Ovm`!}B7^d+L4b6Gyf*tz739T@fzX2;45OoNK+^ zQ`MvJdUk5H&Nn{?NQcZV#MFHtbt$=uO%~L<1w56Z`=SFoH2jxDWKBEv0QEsvIU(AH zVkb;i*vYFjVvHnNfrW1e^-RWfWGaVmBDCs;$ZPXEo}N_m_WUAQeY~>gE$gD$9(l0r zbacVeB4kHXc@DNMdFL{9jZZ{;Kv8baMohTXkaNxBXBSR_;CF?m%aVJ%FK7JXhxv*n zZ&bfzk9oudb@2RvXQdS@I+>`CXO2tyd}-W?NkX~#<^HGJUfq3lSWsS>xANB9`DO($ z=&gNylRN49<;*9!)_e_d{&=mJi!^^c*XexV3ZmMVC_t<;d#gOp)U1rIy6ivjV3Kui z;vIGV!v&Kdx~^A8DtiP5=HN*o%zrGt@cP?U?gqI~?fSan2wvgK3Bzb8K5HR;sOH1$ko$6%I#$x5 zoh_oeaE*=8^r-j@oyCdj$O&DqHms0H<*ofKW$I(}lH+}EE>hM5udg8RD)t7?h<*Z5 z38PGY%)6>vXT7@xHfsSovID9(DiWi}u?%&e zpZmy)9J0leb+n0B~0#MJ-$l#OUHJB6c^mf*%LO?C%?{M^4F3x;&FX z3to0n0olmz*wq9x;5Ho3*tS+#U0tOUt6T{RYVfTErUPFQG|G^;gdFN=U%TH^wSwfz z5Q>=KQNOk3ne=(;c7ozGSJq@6xbgYg7PB?K7@-|`)k^#r+QLnga}h?C6~pSpKa$mZ zxuo>^PL9bj4VgZf{_9PF5Adm>&+njTm=q8$;9_*MkPk^lUwDc~da9Wjt z{OcWSJ+cq}=y~96s1WqnW#V?tqv9s3$+35oo~oz5=*K}95UxG#=Khbbax_kr0h0;L z%j6|Q$a3M;_Ni;z25cN9uYAd9kaa*!w0}vWu)^PXd2s&q>zqBw)OJ zf(}1;vAQ-RvTiqBcw9}vZ~^IDy^lR<%yD|YL84NXyQ>_Q44>Z(0{*t{sBoo0&z8`c zrRGY-$3Z<+p`J{Qccd$3?I)TLQ;n03%yEZCLajdO9>W~-d(9bN;0Sed^p0vA?-VA4HIR7+O#a_jzEUxgC~#xd$J%Q@iEv z^c7}x_a3@8j84i7SdAciS3FzkPB9oSdRO)6sQBZ-$cZnDpDGuriqYkYLs1rUgZLOK z*{C^5HHBwb5|A=z5S6ke$9g;wK_V|f#3zdF>U(k#ujnY%15tkEHTe?<2)V(Qz)Ze1 zFqGrKUvh+cw`+C-rtWW<<+wV#AlI^bq7&F|ukS9hzx#T}-4u}x?PbhNLKHr43< zfC0N>=7|^u#s(W;>i^V`bQx@XJ_>11s|#*bLOP$RpSe{r1I@v5CzwMdw#remWql42 zHD69;Lq0R3bX5{?9}$o~rp34RcEz!9{!_GFHpi9Oh3>-D6NzuW1OZj5v2 zf44siztBD4KLh86oC@eZ06l$vB!Wi__)sPAT|XGLHYi0GJvD)l&- z3-eK4XMvqTW7At#zwCUWk+a|FB^B&D zWZi1N&~YGml)j(NO2wZEg$lTBj-3>72|iYa2ER)x4%01u)j$O&d{vSQRvxyw3c}be z(+XZUx0G!}&h)Jd#2d%^<+k6Zr`ACM`;-c4GCvL2_zAj-t1SLTP-Oms&f*80Lf;?n z7c3@aty@oL5pEUsil=T``Hj-OO!?RGUA_9f04WGTUb?6rM%Gwnj9~9$j6tyLfi&@; zl9)&(pTbU}|I&fnn-I2<_umKn#Z1A?hrbphpK=mGDgvCUV^BPu(^r772!PD0^Y7q! zWGEJ7YjR098TFMwtZ%vz>nr^j>(f2_9_u$>{1c>fDR*uVb0`6&4IKE30Oj%dzfu^0 zMPGXhImS;Pg!0eeUftMC`S&EUDwhz*$^us7yVCfoC3;Q+plr!IJ_KQ9>5pJK9Umz+ zGj;pQ*%*d8;XWl*e_uetCy4aqACREEAPJ-cBSIh*$OIrX!Z+RJK>3IMb%8rCXRcbi z0X^sf@Md?JLXYiR<9v$dH~O^AYAav&xY3^WOk5&)6%;{D5Ez z0-TKwj0k}R#%D4hEaIC|x46&K+N&H^^q*P1i_b?0tpg7bJHMxbPL zi-aJ?;(y>>Ah-0Za(oB?H;{YM(^P@*24`2Wdfow$*>^F1NezAFac{iOjIq(*ck4Sq{GxYs0AYimf;#|$0!YMQ$OG%I=OcxrDj8pM zjytgKGg*fn*4ep%)Is1Kb7*rvV9IOG=eyXCf(+i_A7~fOFL*An&5h}$ZZZRwar%6O z@Yog|Kh6l3Y!;IGl2|ZOsEXfPs`mf8-6XtzWvi*($Wy}M7jz>8TLPbph00nS}dU}_q?^*YoIQp}uBj3H@I9{+0I;^vE17U`s zJ2uA5THqLNAft{5YXF^ojr$erx(a~5mD~?I;%@+85aiJN%)01PXQf;3N$fM|8Y1Vc zm~9nqVU>PaTNY%05H&Sei=@yIAIYz5!J5d@t(!-2*_QmGhWP5&P2eJAI}^KJ4my6Lo(+VnX0g z5GvK)ZgesLDDy4Ox<-*beQ%fX$kzVDa+xet`cN`Aej_2VwD{vS}u(lk`mr8$)V!Y3*ytw_M#{Cn=a z(A-%bYy9+j%@9!bcT}Wm`5PB!(5EE(EKDlu?`o&|U0|G}jpo=N#@Vt}(16>qlZWq3*M4NttmV3}692Yyz#)1C6;@f~F)Sqhv50FAEiP5dQ z;E^Ah6z7$p5E0#_F|bKAKB$82P=L#3X#=!zT^+XlUBFxq3Dhg;Ois+&q~$~Z*jQm3 zRTnr&iln}E{Uk-Z_dBG?H>V!RxnA+%JZBd4lHq!xLe0rO_Zp|JJ$%I2i$45sPHeC} z-2qJO=F`H-pC)!HBvDYpd;DGd#n?%a9^1=W&HGF&Vnu<76@&FQF~RK{laHIRb==)w z_jz`Y$kC12)I+mD&YJ&Jxe&|#a5(FFGpLc0w&p~FicTsJ@l3J6$w!=Vt zMSY__42qyML7nlwz27CB?&ngS&2p9M6Cp08J<=&bs|&;BQ;m2qS624lJ_6NJu9pBN zQyQ_L@OSjy-*paqj{%`Jt(MxX+Ss7dCuV0*Q98WcYF=e=c}Ov>cm5fY@OKTRfF`IA zx*O~yx;d+Lg=pB*>HeInWk_M}5fy+hnLxO?$r@C|e`$6w+U zK*3rDKu$AKyn7G+{t-|{84rQJ)Mp3nGeAk#zTZD`y>~imI!7nqc)+*IX_|gt?lJwI zXr<+YBEx&q=GA0ZrdwLB_bn5J|7Q3(s0N?i?bVO5zJ1G(^gpcIQ^isg7x>y0ma{!wPlAhsQuymS>ELXT&A05f#FZ6twH$x zwV$hprWj^>y!)tsG4p2(L@O#(Or=cJsBBtW-9+G$t+#Y+)<12Sw?zKC08h@eiI$h< zqBOvVLCH4q_e7)Lf#Tr;g-XM>Z$;sjCaM&F|48T#SphBpTeT@dHU0k!_wQ6d1f&zx9e534{ENco+SAH$ADz5gu}+yI`J1xR#zkzd-$`){?q!UxpW zxV+@2x;Tba5w})7bSC}I6XINM)mw&dajyvM|E+a^SPZOoV4EVEJ3#XHYh9P1(NhyR zsO(>M{Ncdezo+$Y)+CwQj3uQ7%;v1hd^o%3f8l=rZ*{|ZI6IZ* zNbBzIji2puDx}s6W}p78!9qcreX?({eLB9hoG%5G;{INd6c*}qvY^8IVJ$MvV6lc@ zA>{9R_;hjL8UET@@aS(t0eukHd4CbnhyewheqF$LH-myz((ji;`P6`YJYMG1WtVWO z8B~gg|9%vxNLp^J*kGo~USaCs?}q@b3PUXb>W+B;G=2I}Q^1bjD~8Na^5x#I=aGhM z{vWnmRsUXodwqv8JE~S^NTBjp3s^+a-wW7wv2t;jikbE{#i6tyh2P)5*;GJ+Jn;Tg z)ZM>-1TR)0=~B7ysZ9}hZ06tJ|5B{#giG0Q7U{BFC5Em?r=jeGOFyn=XBra=S2Zfqv0?pyLY%#{?;{m^n2X%3Un|?j^;~l+8_`P zXehl0+M~4ynm=Noh6u;gvww*BDAQg!Szb0av%7k*#1-^RY45l6v0i+ER*37JN}X%? zY~EAuGuPv-W1sAB17(%p-;@X{(Z72KW;>h3+W5~gSy>-bM<-k|xu0nwxh7IgA?q<{aOGz^*6ay{!* zHRv9@DhhhHra$ZqLUB=FE4Nf?mx{gf>^RkuEJKOF{Z~U+shUmWV=(PzMowq(ywJ%) zpIRY(n0uq#M0qi2IWZjEX>jIaQQC@g7bs}w#K-tgUGKZoFQ_b$FFK~aE-dLj!2a|< zrlb3T-evpdK~qY6J6OWV^!)Y(`Pt5+BKKeLuJ=(*CVX*U?%!X95xR3%r8MCX#nNI8 zD66M=z)V|9ZeD?GzSH!#n{jL~`?LVBlD#b&oGw>Vzg@|m{;O4XE<&gPbKUtqmi7se zmNX8cQPL@!-2D~vwPM=HULTu9(CLx@S(Xthb6j{Ez<)>d0yp|QwrPT>^JdWBZUcNs z$A_5K^Rpd+N4}4=S|*gFsUKPcfmAbSYDriHAt+LM>DJ5RCZ3UY)x5EY)dSBM>YnaY zv>4-)IJtDPhRt}-m|nBXnU6Ov9LDK2)v}+w(=A@oILfEY<`+sW62mlIb?{Tp=6k^V z#&B+BvSXach^-M@9PE!2>oscQJ$6s?q;QAyY7EbC#B1z^-c>Vzh_#GyGk}G(vuxKq zXk%*bviX>G-4!QBdlUJr{P?WpL*Q^)fX_KK_C!2o0QB2xBj35H(mX!O5tnB z&J0ubTuTojV&3w^+A4YGv49h3l+jw7Qd~o1Jv@Ml4$y1_hIP6nZMAr@hmty#JUv@l zC0{Xu5Uv)iVJ zj}uMH7uyO83U+I!%<_b%1$s{Sj>opp8O(rU*1NO%30S6wNIs6kfU= z)cH)`{-x++XU}mE!t1vc&0d_XwLAt=O4FGoOjxqpU(9X2l5l!hltT+Aa2VA0Axv3o z^U-UG#ZhoS0mAH{SkR-HE`g<1105}(*;YqBd!j!EoY zj9PNw;&9u2m)Ooo**ieF+nP_knRShlRc4!su&sGClzQ@Djc{_J)(q&Bkd$9~+vIx6 z@je3fP*d;rOe_C~^u@vSJ!nQUEUCi4n0P~Wp_lmrSZchM z622N3J!&}&n`yF~y`NALKgki4G8-U85SM3cCgvb>T}EXb zFQPu;m-IMms^_8R!}h`*3iIzBg9Dd$(>KHOjz!ICb=e@sptiHF<;N|%>mo!--`$_d z^R8OzF$x$N0F5L5PSq#kb}7z3+1YHvz0`^I9W^^LW<1l>4)UYpaEaA0HEfM+*CbF$ zBxu#D;-J(65xxfD0mtju-2W`qMz_KlzokuQ@N1bq$26)**ey?1Oav5Oh7R1?jUAvbVH*LFo4OkZ|&1aB6)`U70u4T8pEGVcES`G zZygofRd;dz<>Ks<$$6*cIk1#AJ{w~^j3aQVyU!TGOqfLIi>;W;+t^o!iaLoS1k7-gYspjt%BMhViOliGiBxjJS9k-IG1)1xbY!v$@GOjA~c7 znJ+u&v>s(cz$1CPxQdUH2RCa0(5-xn=wbx%T#bjb`xj7ECCH|^hbz`0Qi5=KHu1*p z02dHO1Ryzp4oSph=YO_)EP6X;>jB4&%saXGQrMFu^%UY@9 zG4)d&iDS)zf-RQMf!-)~gA6BEt+OR{w%F0w0QTr+n{ z3q7_!%`fdOb0klXCBoUVpmwxKE5O( zda!hd{Pb%_HyNTGglOgWJ%8_$A`rg6hHm-WOED|HLz~HRz8C^S@GUew<}`2#shwaI zxNzXwtgRO4xdPQ_=0&PE<;Pwk)VXYiGY*JHzh#6Y2Ju#l`cg-aMIAU0E!~Q4I2-%xo@6z#%&{`&nI|e+3?M^0sH5aU~&C3Z$6=T17qDhHhtug3WdNZQjQg&KqEZ9zlbrOA8I0nQDP z1Rj*-9s~z4nTkgAo%dq>4^4K=i-Dm3c`|osXRK}5laEHA4f`2$T&Kfh%dX>$T{hJf zEAv%rOI7WaJ3Y_9;>RMAIASHkR)n8%-y2eEXbe}LNX*=Y7d+>E=w7)QEP$6iMfyuB zO8~Ic4F|%XEP*amdl(%-S0M_}!E5UC_JPrT2n&KS?hw?<`Keos=%12}i+ShO?b7C9 zHHNNOHi1O~Q(9)F#&%>xGO%S~Hp2jBdaRo}Q#*!w&81o4F@JnbguK%;ehlw$3Dm#a zmg10;2leaXBGmO^DIh0b>+W8^^l}(#rzlm6k26~o;~Y^LCeMF1Y^8wi5Rlg$@elJX5`?$v5t#d z<~oH>F#ct;kTJh~xzn3B>HsEcFyfdTv8=`zCcg^efTH95AkcGuW$qwWbU3+i4{YYW zck-T@!$Gar-JjpLgZuHs$I_n5_3FK%krWBKCm3Hm6>dx!Nd&tJP2~<%8Wn0r!b1D- zS8Lx0DzkBxKbM&iPSu_X15OY}czdVwVc>n6Tn23R!}h{(zH)+K;jJjy+^z$G#|P#E zU^1zb7wt7oGi8;(<&y)P8V()f-+mtS*T88WEyJFht42KD!fz1 z-Ht6X)fX9Q%V~-Za$ov(YOE>}cCzA1r(wX;i48Xy;sLe->MvEqZ=#@(6ox9XoOpgG z7Pz!-tHXHi+BjX$`!_G(OSMykm77T|)0033lXfGxM-K8CwB6`nz*g8AS4bKxy`NTl ziLSjEfuXem%d8?qEUnp%Jr^F8xLa3jZ7()%e&T?CG+)^W47bwNDIh<)E9na$4qJGB zLitBN8_Ql096~1tO!yDU<$Y9!?S96sH`j@VjT~CxZ{U~90`7n28TY^*;FX3!2$$N7 z(hifCg(3zWh%n89;d{k$gSd;X6XILufxwzaEY?EZ&LzY{CHqaqtW28Pb8L*7!jc;D zDt11r*uQkCm9wQIz%_TN`oE_}g_ugkbjy6gGdlMM>Wd_vM?g;Vvt4NS}Z^G<=l>T65t}8NI3*EfPlX?i-esIfw5QFg=wU zGi%mD9R{G<^yReW4D$lPctm89WgKwwC3j&I=Se1j|p2YDojYq z=yX<)rjIU*vXT&F9E7$7Ml$c!m}c`m9vN)}+weW*y|>GI(k;&j1-1*4q$+PgG+Ysj zV{)%yiA^+m6Ii3b1%C-++Y_sl_~rKHeNb)~0mCl2gZ-IO;&|)JM$fK{iU-jZG(rn7 zegVt{WlinaVp>wk?9{Ct0Hj9m8=bsf+(#SDZ_Dv)KE5g^eJg16WS?yQ(woQ)%O3Iu zgi8lk4mch8Ax{a|6~>_2P!^`uXp}jpFX{5@6@rGtleMe+-_=f zX>VODMosP8jo#bxY^2@Mox$Y+VwdX{l`9fnfxhXtUFeUWu9LO%@*kMb+;@%dj7XA; zZHTDk;LLu`I65O7hryh6UlxtGiOlRiCO5)>O{RduFfyU&SyrA+pVOsSZ{O5<3a}^) zIUQPEVVD8Tw*Q*@34k#e^$U$Bg^|C=?3uZf5EV%ou{B%vhRs2RrMcrEDW_aR*lXv4 zBFic!O<3bd=7VGAt1h`qZ#MP)Kk|mnVeRGgf!C+NPC*NZD-8IQ`@^1m1sl5$;-^V{ z!jD_3XXhPX0giM$1{-7M*GbQVsVwK8H*4SfsC#HEv>(LHV|A$*hMM<5!lzm*C(wRp zK8gh`;LX8N6DO;pw=D1_Sls7GE_iv-qkAqitiqho6p!M~xm$lcFXO<&LZ)JyYWX`y zb9Q6TcYc(skDi70d5pI9gL8B8y7r+hgxFA*xmGYS%OI!FgpO>ceETAre3;M96v$9VSpKE=;IO>`dTW zG_T-(1tR;D2Ot14-Z$cRByg9fmCuKBEo~-Nr&bgScH2E;xg5?0gF5Hi#0C<_JzN85 z-EIdr0?pIG!1FMoP`MhLn;c+w^gVM;xY{ZzW!*7K;_%Mt{DrTdl4VXt%ts-1ZcHd^!9gyENAjO_#4B2A-& z8lG{(Pr_!|NminxoPF~eQhNyHzU9)y$2GnCwu=@`e^KS0UO`Dk6F@Ph=G+8p9H>Br}v2=t zn*F6egpA;Y3LX;L_GkIkXr_gkS@8G2!}kzm?kB$89RTY(FsIdOcmHGi${fgh_ZL|a zG%(Y(mi5W5aurb<&Q3rT?3!LgIr05qsyCpf@8RTY2o#gs4B&qZ-Pgh2|F_TmQvY*3 z61Z2(j)%bD#UaHtvy70x%Q|r2&;>!6#jc+=($D+>9jQ_Lf;Teb+!%0Po&=(CgCX*=+wfLr(Vr*lT?|+3_7o4h}x}>j8Ir!Fgo* zww+SfHl+fZ$-)12CjTrqsfU;&w`%?!Zrvpw`pZdpA-`7Xm;(~Y(*Goyzs}?zr_w3C z1rOOhw;J6<#fZNinWvipmK$r`;qX2GSHgp%2kieV*aOu2$4vfle4W5az?(1gPV(Pp zSIzS4*?JLs!Ez&n!Rkm*>}+{PUcMoRqk5I&_?uw3=?r*3U7 zR|F6~<>%v5ew_*6-Or~V{zLi@zn<1704jqsUfgw(+!RHy)q4}MsSoRd@Q<1N-_`(rLzDkM;XiREwb!B*q_cnq9xn=LEAItq8rTOJ3 z$Ny29|52L%QJVkfJdKjc)}1H5DwP4ay2T0{%r+gDP!5AOI8Rl$>T1(K`Ajp|1G&l$ zHXw>YzNz)t9Bk++wm=7mu!G7wlGW4gXoGaC0B1?h;)v?gz=5+6Em|Wsz3&y^zzmw} zp;D5N5cYYnkCw20z~w~1gx@eYd(egkHVzVuPkFXMRfi^_A2PW+Ed5;84{Za7pqeh z=_pd+2o-T~&>+%aDpRH-Q-sWPl+3ft!;#7{50Q{5^E}ToL=uwBbBIDnna6kEzI}hs z^Sr6GFO_O+wmlLF z_kcE^>Y`(S-*y3KcO%Dd6v6hVVnIR_R17`R6z7C0^p}waYth2CexM_z{QnB@AiB(dcS7ztvxidzd47ok zLp|XvtW~5>%XmGteT{T}h8K&Iw-$h+!A#3GUT!qbUlwdZu3L z$L;=lE{e!01)JIelsq@POn}i%?BbZ?_$MzF(KvE*d` zMZG1z;vRKvwY;2QO1H0vW{~t0I%+=2WR@D}%oXT`7K_D;Z?{s7&9-cShf0y46u#y@ zbx}Vfm)X@WyWstqp6k8(o;$PMQM|vKcsvSBWf+l8w^~N89)95z-Dv4;iz@&0Tx2=% zLj5u{O072i4%pU9gu&yL3wP+6)>z;|t1m4I;14&pLVHbnqyg#t;D~#@qgHzw2Q2y_IC%GB1K-}A=BJ9)_gk)aaa z9z{T_9L>|bR18F))kz=^<$_&@Co`GdL8*naa(c($a={bS-|}gobaTri)`Ttp(^L!6O$_=Ji8Y(7VkX$>O9auUpGMf8NFy*3ypxgkbK?rfDoV6qP?W;B)}Mo z7jueLR^~DW84rDhR>aP8D)SaWn^HT#CH2S}Qdy9_JLkBqTxPS)G2dk(p;P5f8P@;Z zu&A9c3oZFN2r|+1GoSN<<+v*zwC$DErdm2KKQWeLtXY2EFDb$oZY9bpJ-l96A?43`*nyQQLmSQ)y=!c+X zr9X0)tWfsAzJ&I_i>jhQ$=a^o#%`X$*auk7s+lxoPZmpLZ2R7*d;+IdK!UhSRrV>5J$*em!AM?t_vNwcl?P-!=+5{N~VOzADN3iu%wlU_)D( z1N6$l8r} zS^f>~b`LRg6(!5F!=lGbUgj%~)rMb|&kpb!h&XV#*dXo$^vn+n*YZ+M!_Gt;dYc^Mvndv75OubGv|ZWTla7(^53J3DZ^I9`vdt$1$03_ zVYtdfeCUhM7xG|=vFMZGqCI()AI~SddyU8RUoF7)Lyu#o$j;(_i5{M2BPcWT-eHt8 zX{vMFo^oeI8B&=#2`{Y1=cPu{FBZtx|}Pf~$Pflf9O5z@3V z0ra2Dl)noKH$8SL4l}!Bvwd>Uq`X-U7tFCm5)f`NG;ZhTqq0cmxs(3TFF5VO)m++^ zXrWNmcx|4v-Z;!FSq6a6x6DKGgI!9SFJ`kvh9(mq;LZl)Zc7~-G<1NTrfPD!P0;6e zrbJY}7KG4+d8{1iMR7>ItD(Voq0TS59;qT20w-SYc#)-z86m9aR+UYzNF@sIGkX>beZvRbX>&_uP)o-0F*-z9rIjG2e`uP*}lFlFA zs|6FA0#@2Z>!E!(hPWe)KTx*gdoaf3GlvHgG4xd3E=XktH(NJ=8h}{t2J~xXX0Xd~ z0osISdq&AK(}TX~3VtA8U?OkJaM6GL;+7&qW3wPY^eYMOTx_c#Dt;&ue<~KplDwsI6DD z99fbvzrxh&CKbkVxUHEPSwpU;t9Fw@1uPD(cuZu6Uwg;U+F9aQGscta#>p+-?yZ={ z5@7ZRj|@$&y`I(AuVSjUWHw*yp2l%EWt?%y_fsvvL=^aFzP=OFmY935=7k2=hME<}=xAzGJHJUfYZy|G4RTo5Fx zDHo)*TxsRH%KY$ke|os^-6r!SjqX0;7H6bfR`5)eC~&4Zu9qVt{9j3TfRre6mr6EL z<5PC2F!ZBV!IB$h6h@J%Glx#I|%i)_6_B^+n zJyk`S;xWX7-(B>>a7P&4#y8~=u_1XYF{&xR zOM_z;Bnz>N)h;}Kz>w$hb<3TTB2cFhgVOJ{iTNJfJ(UnVm4qKiUus4oOXyw<_B;<+ z)o)1Ga-_T}Yl4;K)Ia^g(Zn*q>LZ2bIE-EoY9oq`Q^fb1Q9;htrMZ2woUc{c%O^Gq zYH@8$8PIav9XAU3(>>SO;vp$Km;78Zu398=$j=jhz5?ddy}+CvQ*xa*bf|Kz=rh`k zn*^+P8PN4FF)xe$1@GR#_g74*dzg^(`a>3TSgjBE#tY+Aa(CKaT}_J??#we30&z=J zwW2`d0lT(3-^v~t;X{dC$HMerxh(<+a~iwn`O0$nX@eCe9Q*G-pDl)F(LINDkap}Y zYgJ>y?3#H`=0m?B`df92Qaw&bIdOFDV$V%Inze^ISHoTwoYda-tdZBK@lkU^q`7Lez|hndqn%a)Vxu1Z20mP* z<{LYYNqdl=>$!Fok&aYBsyp)c%^v3Q+3$MQOW#jv7IflJ?_y^nl zlSRh|U&_qBz!2FhCsufeuzYwcQT+Loelxa62fy0NF}(jJj@NH}JAqiqKFTKNc>4$J{W=EoLt0bp6@!1-y+61D->I%YsfOtwsq?8Ro*{ z-|wHSnBHiMFQ{t#N^HB&U^|HSpZ4 zM#TQ6>{Q%pV?anV@I5n7w2{6#MMUASpWm-F3$f4?r9kRh#%mF@^Q<9h2!7GsTxd;8 zbj(x7{{SHaoPR2x?Z1LygBS3A{Q8>0Zg)P@V9i$C zMWp8D>`w=2usg;QHTL(qO}5RWqYf60y3DEd1g=Xa{QzO`?L60!xy<+yD-tG=PEq`? zTH&`{GnD!uh{}9*2WWk(2MDd7>s)v6i0al)$={d4v`QZKOBG)J=$BHL#EtZ;AA8yF zC$wy;*Iem-zR2a>*3z2Fw&!pw6q0#n`FF3aue-}anS%Ge9R7RBqV`oy(fN{Kh3|TE z{S`YQ2MC(L7QMQzMgK4Va_lMC(mOt~?8vP*;6qsg+ihx490Tkm)m4rOp0AU}N>1{R z15fz`?I_^y2pwK9TqL#}hMAce$dC!pjC$QTYy&eP+K#4s(h*IENc_c0fuh=|$kYooGDl@ei7yq3v^-LB34fS!&UadM+&ebE_*(kv8Mt|ls+LWWRLz_88dG_lk3H92D#?N37)OVyv0dh zAKW%Kn!F&xxyHO}{WOb>rTMY9yPI?Op45jL6DN_`KTtmAxCyYEpni{`wP56>O#bB{ znpwimDb=9%35a`N_+i^k(q8jT3-PWqfvk}+L;r?a46^Ifb3Srgq=xUCGScFNjCdl6 zZH9TFQ5l_<^u+uqUMgb3lHALcH^-^F()FW)L|2FHI!s>`T+RrFdcpMQ7V4tV*@K(b zqRXEpvNXl+Q^^HNk+22nZAnz8F3>=c^J|xxLR`7zSS8d`Py1`0YKl9%!mHNC;V0hq z-tRPydA;sPW_x!tb4^rVo`t@L`QLgK6J5T5HoVtzg=Fruq>hj|KE z+;^&str`mzV;@)YPoAk+F3?*nA9v}=*!?iej@7}-Vs>fi$LrcEoZQ=cEkxse* zaGdGaRaqq$6*Kkss>l6ym%qOWbEhD$P#7K zm#)4@?P5++P)_ret!Yz#L0u$(0EZ{#Vx$U<0LscWQs657d}{=~HdXBr<)|GRMNPXV zuaR9kXll6msLj&v!3U^V5qKz?(K=TWyfUXZS6cIlO)TLCWN{0zu~L7PBB#{gd*#6F z$A2|xht5-7Rlq-%YCDzj2-QPt5ubRH7!D#ETg*^`3Y_6nw8+mBlOO-f(51pev@62= zd92_mk<3tD4gChEOWXspKi-b%Nw>sz7M zuda?<6&?|`8`YZ2j*Xqk&GazM%*+}cbiF5)Od*+|OVa4fz;CJ3WM)llaSaM1^Yuc$ zPQq-ru%yy}S#}au%>LKsFjT)}Ka@)i!^9E&)eT?z1|x~STwU$o8!LB^pSgo2LF4?o z&t%FKQJ=bM6c7WInnO8!MBSRU)s}c`#fs{QYH(3!KO||Hl`BgB3$aCZ3T~yTnN7QW71R zuie{S_!h487?~$DyW$E>4@aQndjb^0q-FJo8Oot3>m3N*B^JPq+5*@U&a()mkB^&~ zJ*!=zUxfQj=#Y|A`0;{Xu_n|}40V%JjIu~P6PVqB0DQdz`e`tE?m%cU$MNbx7+)V- zf{xo#)57YIm1BqKUiDmWPK>tmRK!Rf;ZRLe9y`8SxpYdM_tUy0Oj8-XY!>;hH}F?Wj;@B(7}yBRC1>@ z8yRY(U9K@`Rl75cjB(i%y+ukwL4JP<6kiS_RgPsDss#$ za=<92-5n~SEOOaWhXL}20bpZ$l^{PUS5?&D;MajSKS7ZC=Nw{0VdH-r=7s&|82)n@ z;a^)UhJck>zW~#smKVpygm>2`N6$<^+JBLF;ATHm+)rLQ2Qyp>qvN|!=vJ?rP;21K z+~uReOvc%9^G`q-$@rkh&SnKj$J(@w5P!K_swBG8y)x6)><)3yiShT)SCN0D+hKmN z$Db7yD&r>pgSRyVs;nBcBQVAoJe9sr!ZOr}KD_~|8FvxDt83VpCFyj`myjB%dCd@h zygw1w?cAS#|Lth`fY8tUch&3^4xRvBni(ZC{PRY$oN)3yo_!Smd&~awdXfp`_u2kuwMdk#Xw=6|P7$n=?oXbmzI%~cg4>jACe&7 z69`$xC;msutgM5}m^20Fd zGG88$kaJf*qhubPemsm9hNx%PitKdfg9531{02zUt55kE+d(yTNyW*7q-9!UtU7Y6 zm$$`_oK?MU#a-vn2Lq4vl`-}1y)fi&;Qh|26{&s}OA7T%uWvXi`bqcs?=NRE4j#hq z@}CcW2@hhQIRV9AGCF1$Bpjl^ewC3Vbtj@cBPk7=gC}r)bfhH>!nYS5feVDTEy!hvVezU+8W?pcJ z_=ul_vu6iDV~n?RkCY0(zt|RP`n98;XBb(LF_C?<^gqW+l`Uay+03zs@iUu?3#F{;_K8cJv?(F2AlECNd3! zEPV!Pft{>b1jDf?37US@9Gvn`;l zp*CW)t${p2j_@t7V){(8qvl^r#f(_0alN}4)dpV;?S6btOxY4#Q8dU0YH=7O%Wk7s zdzj?6`c~`>NA7jn%oxdpCUR@PA7GZ2cp6c2t^kvcK)Lr<7#pPQ`K9>_P=7yGp&8KP zJN17)Vc!eiBAFWP5?mRr_4F(QShD=B`_`Dfz1 z9&0;7P*7G8iJ39Wt0~=Et$OrNi0VtBo@Q)2Q=~dz;15PFM)e0NYsIvQqI0NGQrOx6Wjjd4`z|` zu(>{CNu3@cR%k08O5vqfV27 ze0b7^7AUXLMBDjhc}Jj+{NU?1IK=#5Bpc^+`1kZxlBRf@_ov@7QVc7)g@PV=kb=+G zO{4$oNB?)ZWX_Rf3qfMPAqsF>(?(U^`|GQpBfh%4nLC0Qc6Tn;7ejq6U{49cih@6% zpF$9~LcI>E`E80zseM?k;`KcU6!i7qKU|PLb-(i$P;@S}<+uOFgBX$~tTj}7Zmikj z_d*3i+ooTRcKHwJWvZE>Kl~Q1xlZ1@tBNv_ajLWR_F-=U0p|~qy=}Y2Qa_*P6kN+y zj*AO>!L+0YTV*vyFO_tw%&R8|6MPt`_1b=uT%^oip}L$;#zKucOF%82Dc`siBA#8B zNy`5bx-7g3rhU400PTFgUHnS@lQE)=_qhfqs{*;CTNGnW(5>mySnCR-vG8pA8@D(P zDrIJYdS9W5Ca=n`vlNS%zmLB`6(SZ!xu%hV1Pq8PTJD?B4<|a54jB16B+atJq!$lNU9H6pLvDoJgUGd=q=8y z7^pd7DZ2gH>K6ZmaLx2>2QLO28PYZ2Qmv}fnph{{oUhQGjI@3jFeHw)%>KD{ zJH4&&(9)9?s`!&h+?OUdPXj$08 zF{xX-Ih$2=wd1B;wLk*7jD6VS1 zN{1|RY1LCKG;N*Cp4&^&zq=Iva~=ZO>Rg3l%;2%(N4prFYYO^{wWBnk9OBQXlr?+& z9rc0?ny&6$1eg60E^ z2G!ajdfnt#M$s+p@P@QGd*yLvHcy{&5-O&nTiYS{qP^(PPQEe&VPH)8IV8ql5OFKt zf~ZxeBTiU2jg(HKPT_JrszkJW;pBe2iUZ^)O!ac=OLBj1KAi!^`L49!H3!Z1pVw-> zM1=3B01&EWLs3tAH90?LG}X0mU+k0*^+oMoHXCj6KJbJ4nZjd~lhXI@Ul1yc^Org) z@!#Ve=()uX|EtPI<*p?2`2|iTFj|EEpA3KyBnR%be$WFj%w>k0Sc5voyWAy5d&M$? z$WSQjiV`sNTV0Z5G1H!9!nk>IwqedWYWJqUL0Uo-Dr#=PnB96Y|Ngt>IK4I_l^n@B znz|{vB_=1OM}Z)garFP+k{Rd$L`W9XKjts!J@o;C_av!%gh6RFYQk+4m8`m!3A>f- z7d`nf(+1xu9{8BPe?P_v#WhV9$p(iuAMSFWWM(K?fXso=2S^UR8OLq^pRGO{18eAA zjD+0_Q2%j6Kju-Kv$SQ&(J$PY&&V2CyiV`IK-PJl8i?wL5hGH0Dfe>2@j@n|gm&Lw z4=YxRiW)j{{LD9sho4?*=a_F~U87Eae!y(*VWej2PgfV=jaj{aVs#RD>FIa17pxvDe3`4@`P6~nWA6{Ck^i&4uc-3Z?5Ecmtg3m# zuc-OLiQGhKn2muxB}l{_sMHjFFWY~PGDOA3AkW6xT2_AkM$epa^kbaelXNHQ^d|?F za7NIWBVMA`V`msVE8UGUB1iNG?cnV{9io=Q&1(kOk3icPRZjbCHC{irJ^sm%jZSD}7%f7`% z!umNGp0NW8L*K62nv5g|rknl~R{DxPK@xnb{_=qni~^sJpB3sxTAeH;hb#KI#^ll* zq&|0!@PtyN|24n`mQX-s56O1S4KUuraI>hhN@1RH$`GV>c-bENO&>S^zQeN?0P%E9U1 zNPx&&77wqLvz_k3NBx5erC}EjCHO(t1=Pp>mv3W*Iqg_M56V%`J$HTp-Z##;!FX@W zL6fxh0=^4cC`rt5k}-9J+YGywp8i`rYe8Ulw?34`LsH$o7xYw_4b9yt&qe+;JWkkEXzLZ;@BHoB z=bqmk7t|2XO-Mo;oQQ`k#(gDfDDBmZqf&w)Pt@E>8t8PUUEF-Kn&fKhs*L0WhLdxd&p;^X$($? z0#e6lw}~jz);&fL7)hQayM`#94^b+ImT%cLK=@ zw4T&~NbMU)ZD{$yMO6aMV^;m#ROwtOt*n{b5ut{=m=GVxOc*6$LQw=RhW`(|E6m|bFf0z8#jd&6F&GL-Y=nIcMD(@g~m`QDL!t?uU zM|eZ%VW~53>&^{V1&h*fMY^;Em8>I%0$ZY}u0TV;hF~Xqv+wc1gl3W46PAN;&$iv$VDqlBYB!GJ^ zz$Rgm-2esf5GjuTu~%Sg!b43%nutBA@Y7MpI7%+?y>O^49oP_Bl4(8vir4}i#QzB; zWci1`kGxSEF{jnVID==|PZq}71yoa2nF)ZN^w&Un-vYF?aX?gB?yn#sM$+>HNYB4@ z|B9Mj1YM;iXi9C7k-dr~cM9O_1hOJ_aqI3)=#cg5U0Y4fN!r^Fq`su9E5B`*KR{?~ z3=d_K+3eTtWS&6%;@{Siq46l7zKw(iOS|(uS@*8KQ62#+tin+@ENuK4)2!oxh)H9s z3c-MUh=G3UP=(%9)$uM+?`kT5hc)=>U;z?x-rHe~d&YUyw%nz@1Z1v#>t1(wzCbR2 zaPpKPOM88cz>>DLzW-1^mMgsX`P|UtXB@%j@lQbbE~)hjBi8t{>i%2lAVQ=nP*aWo zAh3XROpG;6#KQxfkvhO!4J#Z!1kKonVSpf2R@5D27q}i&;!q#abuJW%4;p=`7r?~* z;*^{pG-9ZG+B59BNgaUYys00G;%Qqi3tx!X$IB<*mS*FhV&s1Z&> z`9>3MMmlV?#6#}*Mvl88`3B6J9)^}~S}p;%Nyse^DG9HrI)V*ynG;x;8LO>*Cq={B z3~f()4@(XIzD0Nd-7uG*H!jrfdBpdD7x@me&@p>K4`<-c{F&$mkD%`tdd0l|@)(_2 zcEK}k=;g6v2T@q~0+_SM&=0M62#}Exxchv1cWYo97mB+S|8S>Lod@t=n8y223#uJ&y*(}~D{ynQ-*5rS^OtimvW%#S*O4X2u;_)o zXIn$?bfd!1TxAH$y$C3YO2YCJQ1ACU1e?=(+> zE>yl#caUar44&2Vy@iF$DLyr3Z z=WBbFHLxwN^#oN#%=p(2pzEFC_mUmo*=9cYM8Uqu2d5H8j_thpj+05m%}F->9DVZ0 z?)$?c4ka$noFyq=jzYgxJ*x9Bv$<_1Y%z=lqIX+T@INa3B=~&=$oR4S&mEeC$Xg|f zTr@38da8e@aC_BgUM!UTAT4~J{K!Xf0l!orCR=LXsr11nRbeM2^S~<)4gQKTiZ;3V z1QA+45N`QX5+>Flbl{-LhY%JKrhIU+VsBBt8)Xn0x$-guQ#x~i3+;@DaBes)-eXo| z`?bN+1eR{Mp?w~_c!@LiTqP4Kg zvUNG#RJUludjy~5AD5j%h2@kEengrR?;apyn5^#j75Aj4(z*ES&JnZ2Dq+9nG2>gBX1-h z?aE({GRv(P2Fz;;QplJvV`>?Urb$Y~O$o_}A*Bo#Vr~5vZvb+!8Z5!U`nW#$vCpBO-&C?` zwX{3#TqDsi>RdbZB#J@9%&^^f^lVfY^wPQG2(0@zbqCn1#7AKdS)~HMJF>%a_p;Wt zUf}Qvm%k!AKyxQ&rP$>RwXp(8)bQjefmty;yc7iTi>rx`I^uFRKD%517a;0Lv*ue7 zI~IX>SN{WM)_k{OrtGV8E*g{aRXWobG**2CsoD%2p;Q&~E?W}vH2VR`+PU1SVJ)*9 zwaR!6Z23*`Mhtf=0!THB*p<NZJF*nc~@TfW4vQ3!bnu95Vd5Fow{7-+KqW- z+yKS)V22hkOmjhk)2AE(hT2rHQD^o7 zk=8+3OiS3~J9L@X2&G(3nu;);56sSSR8 zY!3O&amht^7en~=ZnWWG77c|Y)AS<}a!)zLc53)D+B8yI%o_JHCLZXF6GLl7Ye<6z zM7t%r9)-3$C@r=m{^W4rT01M1ATLW6IToeF8EiWxeRTv(>j+3gXU7h8fLd$m8iTg zj`phqN9cYO1r%X7FB{sc%aX{dC$mi%PhR8vbwM3<1)Jaz$6`*)4vSDHOq$jKorv}X zyrV9_F)KbnFd7!HIJ@bBrvC6utZ4NQtV$nsKf{`AXa9K(KBT8ElL4HE4hcDm#t5MB zhbS&Wf^Nh>uibmpru8ZzSqJkwnMAh2F$=CCcU}Lt?{yymsumoje_oDm982d2`6bfq zdRE*U2~!`}dNn3ZzW4)}%7U(&R2SH+ndx{>kG<~zd)75qm?&WcffE~nzG3RUI~qDC zUZ^Qz>YvhC*5QgBQhfZc*tr{#kS}RO3oih;KzD@0o-MMS?2QVTiCGr0_=mCVoiB?D zv7urrN?AQNd3=g!K_3<&j1;qwpw-W5U3&Zn&_k(|}X}u_C6iBTV zA~}>>^ZPdyPX0hS0B2_p{&@F*yL(;xIsxUV0%BUrm~xi;dH~Ss(h-JiI(&vQZF3vT zkF4P#9eQ2s{fgD?I0&$@GPtKlt3EkNk{Cl0 zD8z_elM(Jwz3bd}EcSvba{WC>a`J5kw{sFWsR-}U#-$#L7`tI1YF9SXiZg!9(`U|K z2#J!*x_rWynuD86yhD~F1bc~xlF*1cFdQ^u{qDHM+shh32I99eIYPeL27H~x;tPFj zbgjQXjW|97(1#<0kS{1#16walBw@UKT6Ct}H@!b0@dOIASoe=Wc(Fk^WJ!DQ}F zX}m|{&IFaa9UK&B2TGWhaEv@X{t=nB6jAI13L$1U4PK+BbSx{Tw#nZEX`gixckTIw z3>@sU5c##C_a?7{vxcM+B=%qarayqP9pE4)>7gD=rRu2-50{9HDyIH+eKwbq<16>o zft?-5xZex`j_#g)B8*5b+uACDsemYE7@DYlowH!r+1~AxVcs~zMRscZs@i6fC!|rM zE+|J`6kqd~C)XMGIGu!(s2k9?x`A+=PmT9*iKnd}R}$HH;F(j>%IeQG;n{pVn)I!V zn^-_yL=S*0(=IEsQOu3=D}0ds2Npfb5muD1Y{T&LDw$2bNlUhHRG9P^3r?&5J|{9H zsM&9*3CQmkm1uJF8;*L6w|LX+bs<4*yF|hOpJL4OY|{%QO)Q_ycVahl;UcrMJZWEp zhS397<%aY1M!Q}I4-F%6I{(i%B;*SP;Fvka4rqve6DM~H*UgC{PRO) zd0PZ0j-ZC$SPS3+f&}Cv} z=6(>hOSrDAY70!1 zs5UP@6(78`E*Os>jtfBFBar3e7CeeD0Z6SqD&RRScZ=_L4$qu?n|8kVfy3C?d?*?J=$^_l5dpV4tCu9dGV{U_wZON0Q z?2qvC>z4INCXjY7(OvfCi!_2Z$?->Q?nMGrZu;Tqn&8+|I0B!W-Fe5ets_*Qf>B1( zBq-S(@Dh0ovQf^rq6^0nMa=CDA&c15W2ys0v4e1N>GPFU*{MJH9_Yoc-=2Dbp-`hp zxV;o>D|uP`=6&8Xy9i|X=1k@(xV^ThXq!pZOXo>gLju58+06!KQBT~b5{RvvYpn7$ z%h?1SjB^%^Vt)}KNDNO-?d|vyfl&Yl+Z657)+H zA}cr9F<+^Ms?+szRrWI~tLlx(01FL`PaHF9q~W!kvJ`8ppYH)Sv3e!AV%G0&oLDsjdz^>T3EDh=pAzqu#&+^q?ke!S@K!MlchLQ@3-RCc>K-OVjtwqCl z?WDZMH}uU2X1-hNWi_Nu^}OjWtaqG9+AD3eobodh$WHpgUm%=$^FGtyG$G;`9h(uU zq2^Lt2@tL8d#tfvqop!R$f;tUpStYpQT_rE+8AFVS+;1LGiJ=a7O(REd&;k{*3Brn zJ+vFVv|Sc!jaDai1#m?AOpvD?6g+)u_g5J?M-=7_rwT9Y=WSFm_!EnC@F%9=d$Oo6 z?0zr5!+Mx}2GE;73z<&*4fsACq6L)KO%geqJg@ZR(*a6khG;10jS(Z0BjxoraI0Cm zav)4BtjkfFAx)5QuYfg#1@7Fj`&s9xjaf)IV!s|8hkR*`b9}weV-JukwD2W5fJ4Le zYFwPnUi^9l3XNZT_hMg2`a0N9P=9`JLUBEnNeXp=>W-+@Zr*!{c85VfHib0qE~Jzg zrY1?7b5GqE68c0#)%6g#Z%ar%C!4~EcD1<>51!*AfMR)&#igqcxuVZh)k$O}^3^+3 z8Pa-A&m5&TeoQj^y?xX>vzF{+cwK*i;Sd=o**OF-+7rLI0RhhK+C3jxT+K_fO%GV24GUF`;>*r0_TkYDw6`!;I2W*U_gPG>XObVW<|9haSnLpQV)KEk0 z$(H{9!(E0nUd#-d+SnFWNnzA0^DUXZ0SVWYi$qRTEW-6{Q!dnf+%faqf-PmZO-)GF zF0<5oaDL6|OGkO!P3p%#)4uGnf4Wv|V%Ms}kXDg+ubwsJG#rPvJ6Ra&4?YK$Rt1Ki z%#|p<7J8_XdKC8SL(l>DD!cEWL`pe&p9JC{LJ+?$9XBWtcu@AGuUI$@9D?76RaXFL zo8|Bl!?WH#m%I%i?(X9Jj#>PG!&L6nL&3bpQ_QqVRB8>iPiu<|zNf^+o7VRIt{Azy zFqRzOR3lOyG?uk97AM?N!uQgs=M#hS?RL33g1d zX-wT}sbmlKKgv7~>v2AysQgo-cix*OV*`=tjjG4VhRUTO{jep{@I70)3~); z$G^_}R<&fkBMFC6%AB_6UyaHIckRjk3RbMkMEGH#>{<5@t=cf_h+x?d6=mXeEMku zbMIDY@mK2DvC;8QucX zfY(Y6(=y|l@PGH(g|pv%X%Py@6_N9TLi4LxH57SNf>x;&8JuAe_Z5Ndxf9x&IuX^& zzRai{!JVO;pSkSI?TaN_-NO=GL>SBRQGZ`s1kRuWNR6sM|D3$Hn;&iFfzbXA5}4!s zaIDG^Ag)T|RwH>ASiYY@qPtG|FX{E&I(HyxcnnQ5{6IZ5KMVqsQ(3g>Hx}Lo3C*5c z15~>T(eI9cm}oTmi7nBc7xaCBFZ(}J%Y9o%iFi)LJ;)~GFJrAQ`iB! zTyf*8*P(q|{^u>=8T&4XN(`hC0tgux@lp1R$gpJo8lvD826!QkbR|(?b8T^vPTy@w zk{352GK7R0hR>E4Muv1D(hOQ`LC7ZP0zhjuFTPRCAl~;56^>{sg`wYH^DHhmHdwRRH~ORKxJOZ#`;382XCCec>+{3%}yoP=o~{EKFjyMj8-VFhnQUlo$0Z& za&J#ptopp;C5OZvlPqa$P8EZ=nzNsn4cR}ZKcnWl*Z2Q36aPn!jv*z{CiPc4Ks zN`E3ox8nvy>VINp%&|rHMS%j|=-Sc{Fi;jp+NNyFdW2c1{JBB$9yDb$m#*mn9U!Au zD$acLlrqY&ImiK_jK_RA5LusPq^8kQB<;BLjrjY4UbGZkFt!W3C$afgxw10`A-m=p zOb&5k!9$Fbt6%FOqE4#LtsZ>&lWYnHFQ7Zrr=eMgv6cHTFnmHjtZ~QXo~otB>u1J62ojnop+FYi^M!h{ZA z8o5AnHdc)8Pbn%a_U(iJLI(aSZH^+&C*_X9Ll$5>j}WBS?D%#Ok+r@A-wL%%LowWc zX9DZ+Wj=zB!edF_^Qlou_7s*8P>_n_i8VO;Soka9idXHyX$+ z^V@`cHruXs$*16LQi|(HD9Qn|ZMF)aWV%^CBJ@hkTSKz?1d2fp>Dt+n6J9U~aL)+j z&ofV+O>UkK2=!)ffJ(sr)Z9d|LfHLEgd|jO3ra^;6$!OkB|nH~Cwkm(Xs6}=FhPcu zjlXmf7bLu@fz*UX_(@YC5{tb(V5vqBOkyPKzLZ46g(F!sa&G*_GI4CVV%~B^GEC0N z?SH~Vr$Ft>v;ahq&A8(NeC#=LuVG33cRQ4auF3wZ1vnMSgT~FCDz?uev$rM*j4P|Z zNWiA?yy8=|Bgw-{VI;tXo^DWA2A_$1g^&=q=nT%Q-qi$2PPceM=g#(vyNWi@y*EU( zB3L`k_^8xD9h!m`3z8Oy+!sDUS6aTZ{uK>W0Ha{x2xl-`h=DWVsdUu3=t5n-4jed( zZ4Q3*LmIcKn%ScBA7CEiQXU=4<0qgvj56@?Y^6wex?9_*M0f|mz+4kOWOXZ43J?@T zjafGf9UMP*62}$%D{g=NfGH4MEcElC4I#>^KO@T8pu&v*3a?-(G)F6CE0*g< z+m{rTswgpWnF`Rgk5=IdofyowWx|{85{qUm zNQf2_GJgsSL4tXW*=qzvlWUe|?)?&JXKR59;|1Q*QArgeJ19bieRBACW_f+^6b0S& z9#q(!V}4uaiDJLuE|nCdvQYYvqmG*K52ucQ`PeV1IY~!B&7CWTr<1xbBEx9o^DesS zHzx8#ZpQ`TJz%)zo)!y?MQBx(^p2E-b-A;CW<;G4wvrG>5J}SiJKym6hsRrh^egYb zd$tjEti0JT6@0dM%`k+>^jjE0JT>YFjG7HzlA6(jAC9-(E18 zyawedTBs|mz!`v=1LRo$#H-XuysDxW0dhC7K-8I$j`l!mhH0dZhhD7V0e4(O!kknQ zrhb#uQ9s$%hTm5lza-)-^+oRrAvxAy8PWT;9zyfp0c#%_HgwLrX>#Jce0hcz%&2g~ zYyo&OaQ}GN{+nnhQ?2><+&Is5uc2{TlvRw8(8wh+vHvm@Segi`*8rbxs;Gf5h#Ahw zpN(~Hc(FvO7Zm|Tx6X&JHt5E1;Y=Cx?PEYmRGlX!2i4DGbrkPAiwR2} z*=>foX5)EjBUFZnoO_S*k&ZPxEiSCI1K^2j-zTjF&}6vVYE_B1>EzoJbox)mCJ|$w zqg5rPYkq&+`{HG^WgeBKki+b^J!HhOkauzTmy!ya>~MIodz{k*d50J3UN$R4 zVSOsDzt;pvWWPMh>a_&R;DWi$2>Q{K#nY^(z3FZAxKhrCggjC`H?d_g|LEauOorj- zFFCBA>;KJB{%@`Ihs=2WzuM(9d8tsFMXW-!`PguImCZK1M=V6kmN0Y2)GnB`BKPDZ zi&h=9$)198grP6-&U%V#G)}Kox%>IKlc|_4BqC z{*%>H$3%1D7197}Df^D~+|!lgOV=7Coi(tSpnk161YhhWPX;35ox{wRly+gdHMZjP zac}JWyOqW>^;(B7o|SuX!~Vw|tNZ9O_pqDVC6Be?vQ<-2TkyY?F}j3zXtF)R|A$sd(YnAH)pkceNZ5PEY{wT*=H(aiZ)F{?npR2DMrely^TW;BI|e1doq)I!Dc@-gKf z?fhEfcLM=T{jbm^Zz@?Jf_&}AO8#F5$^W)9DDWQ_V=1I1bV@=XJqKBlOJnZr@CS_Q z9jco5jcAfb_K9~z;lmX8j1R=Q+lp!=F1Gx*xZZFfx@_GRt^R`@#wt;5bpR5|+8h3V z@);Tg%r`JrB|4me#KY21?xd~)Uh5C^CF(;3oL=rB$plf(|NHU;x;B?KEvwW~a>V@! zKdE%xXHW7}nId|D-aJ~AHBeJ)XdYXPPvQ4(%AKJ7Uv;7WbAOKT8?Hi3EbbOO%uNmO-cSEtTAlE2y@ApCzkeEGD3#rZpiPZQ*_bbT;?*9@$F{MaiLlyKHsl`rC{@4jYO;-!Ok@6KTaD52+ z1BNZDh4WLdUB59eLkr3UX-*~r^>7lJDf4#6@Wl|o-QuGXkc8;)TBHYlnfLjPk-`)N zf*7NAXXFKj75!qMkKbL3k_mOm2SYkMl~8%Jkyzkch~02V_p^78E*%8~U&=t;dp4$HBh9aQz|+el6M ztR4zwFP^-sZC5tIGp81UVGSHFwml|c{oZ9=^_@fMloHxQcTui)x(Gg(_w*P#h+g`M zV5NB65>6-73pcAl0rb&Nvb32Zp*8dcC=Z_%AszY3J+cVNAUnwcKrdIk7Q12H7Vm2U z1!N!K)rShCVn7qvdr`@lvGa3m@EE#gCkaquwGna14FH&b9;u=CVL2!0Uobs`0l7Xe z;}jJ1w)y45FJesw_V@O#1Ls$B3i@%gnenC<5SC=ywT5?k0>ISRM}StpO@Z>lIXO>V z>?OUt7+}SzE(?9`sP;O=*wCW&;kx4np2&r@mA4;)w~e$`nTC1(cHnn6Z0{6YZN}1H zYl5;Kv5w$Bhx$+MY1nD}U{;nRU1bhW+j31{Ex%+A% z{i)M-So2DDPGj-S5S-)`L*PBGK#hFrkY}E~v>W=%GSExEOwji0;bu&e)~66{!2-B_ zsSxt}Z~FIhq*YMV|84!@oB*1CKywp*tt*n?)A2s?X~`+pZk{8{?EnR;p>SIM%<&%x zk{ovQorjv^BC1t&Otgh#%5>G;-krKILp1)rWB z#7)FnFlpzasPl(J^YoAvuDknLvW-;2(j>Cb(4yVgTY%2rlQVed?rpJe_vOTH=-hS^s+wQNza?6p^L_l6FN0!oqlVT)R6@HFWDA<*e>rP7>ohuxE!fF2_2oqWc^i@YwGZ?c<{=s!RQ04ClJ>A#)TBldv zN9CYDRJWFdj#-(R3e>&tk6l-f=P?^VJ&EM;Jch=Z1MG{)G=B|dohPD7E)PAUm{9}^ ze=?vE9m`r_J~g0gF1>vMhpN67Va2ptT21EpueTaa4fRUj5_f6TbWRdw&}YPAybF09 z%l)k?k9eT3)Dw7BzmoE43?GLhcz@XCtoJE&^N!4d)f~vy89`sHFJv#T$OPPPfsEjmlLOAUNw|Hz#OeQ~~X!?erP!SSRSMCD>C2V*t-}2fI7G z-eVtWddq(u&oq%Jfx+{ijZOgRt^)adJLiYsAWr^~bK$G(Ek81j}s787TtO%Yet&q{=l^K!p3dN5uG~mIquw;&_+j;+~mgHohjj7506RL zwkkTVD$MtyVdx7z6#Y(Fc~Td;?PbkPIX~IqfkgWG`0=!oz4hEIuStblZ{&Y4Pnlb6 zxXET1`jrL=ExtSWcfb=oh-* ztS41L$vx#H|DpB@yH54JUES8*hG?0mz@nX!B;r!rQx$CL$vFM?{QTZNNo%yCL@8%H zB=N_Y`w)#5r^2J~kG#Ox{eU=yH+tl<$In?r9!b#K&Mqy$st`yzpA8xyxvxfM6nfBH zIVt0^{dn+p2Y}^nuMH2(y@2#T34HV=W!nY-xSt@dF+<)T!7cceo@a@#J3zmxk&(mJ zJ>Zr_%;Yt9FwK#$HTBfhkH7Y9`w5MR4LydUH>IsiCm=lI1ekf}L{2TYWZ>tX90E!% zb%)kvL1fdFmbKu=&i4yed9!*ZvvIS)W#2R@u97^O6BEpmt%BK6Ae}sCRbUU!I?FoI zmO(IoDHK>Dli8%MxCTSk2PFiu%?9ElZ{ZEvtb8(hh7fcR9iuUyw9TxZ%TPTITu~7- z*?=C;$B$oEG{ar>Q>g1x7TexmiS0TNaNB$&{*mN0`{d-Kymto3kWByLy(@eg_H#m? zS(RfP$5e_oT+06hoB6-FDD%l=o=t({f-ob6=F>RX73GWJUYBJUeA(8%CNEvqlzLQp zZrEY&ot7SZZOkM6b@xHrtg61aSM>$d*f+y3!~}D^qOh2!AhNyb+3MMRQ%fLVy&R~~ zysps3JXE+FadEj*VcNw$1dn(4VZdzvqgQoY!8!|?Rkog@F#Aa(|_E= zTjXO})ao|wYmQ!7c@=u;&3C=%DyNGK00-}EjWI=t)*A_Nf8}C85#`UmlR{nAyzT4# zulAlM{DEfQZYSNq-`K2DlETbgN-@ii6aLm%`&5gkvqd|^bz!M4Cc`U9%#=&&7??=s zHsZ)%cOYQ6LaHr8{HMxv+q%{-if}O>iUk8n@d`kLVSV1T&3!s!_4*2#8P|h+udiA{ zzqXlnx9^H&L7HpIsM1&0!%t;0mG7>zU9qs%9wz!XAYT}KqVB}iiKU;No}^LU<8+jN z1=C7={U!k7V_TznPvL^=ZOCvcA)akU$;IWKNyiPnpb(2Dqq@SmB%5ec5l@XXdhr_d z^T#0a!7_`(2ktr5t?9k3jACZRemdk*v5p})jG<(LH(PLYSSr5nr<@*Tm5s8yzAc{P z7rrlsl6mis)y(uY=C(KyI~6ahM!eHAItmP+TY=)WYnY}3M(cixX@*dpvb zrz%O#n6u2J>3s0Xi>Q#)=USbndO)NazeVn1NFHvL&ux8?PwaYTP@k;6wPn1p{6~ZW zL*G@ERbTeyF5Pc8t+4msFDc?HLwTAdZquW1jX)xWFF}#7U8)^3jwhu3oqxNV|8)sF zTht`vTIX`*1XCh}=Yj{ytq+=#du|CXy;&SBT7fbwvVk&O0(C`@6X0ZXF_fg+oV$&b zi8zc4f3Ou2J!tOC_GpBTtyP^>W=`idIQDL$qA#V-oj_N5n{C_u zeJeIgLGi0HFML9+jHq#)T;3E_*I~7VX<=ncDpBTS=~pH;Tv|ZS3MFcSLvhw77#--N;iI}Affxm*rvo1em>o3hS?wh z0r4-!D%Bz5RDUauD^BX!zY4k_ngAgbsbteLtF}xd3U3xN9IroOXKqX?jf( zhKySj-zdZ-;B8VqYm^F)pqtkB%{e6!{d)&;*4@wGl$%fsX$4!uwAO3O?^|IZ10!it#zew}?-W`)-zU21JSHHXQ>kxv`R33J)jHEbBs#vV7DaN!l z2MN{!ooxz87C*)PI1-(yN^w|U^&NZOrDF^mOCS5yk{(QM4MXHgaT}Ql#69A~7}t9x zl*CWrf)dF%E{lXg)%M@jM9KvX_b$O}O8XXYA z=K)=oZSU)qFZPa2`1#5->{GLdI?|fUR5^+bdJ2rSI>`s+v>$O2C?e=|yG!{A+rV~61es9r*xP1Xp z_Xu&$~Wd9qt?S68pi1>XA(yc26{e z0X-7$ZF(k_BQbv?{(xPjmI##vR-u}q{=rz$xt?EZ+Ei>w_$H-^)Twvb(WsQswz#vM-{+59b0BwUwK34Bq#uwN+8*|X z37_0LDu8I-ezwz8*KIuBgSJRy`QmiT3`g4mJ`S1Sh_LUa3XwTno7R0)F-zF{=Fcuv zCDuhvqe_PXS)1t&w#`^(*1S+Fj-5nPK4%`@Ob;czhea*DVCSZg~m(|H0u)IK+J ztBOC(O2B#jeUFi5f9Xq0ExrV^7o7NGQ=O}aQezC3^QHm*MZRv9 z1YI~z4?bB42-bCyN2|`^BU3~X8zNLkHE$p}~H{`Bogi2c`dkWB^DJE^4JI?+5U_N!QA6T-Tt>NMuC3q?nhiAUI#eP`9Z>(@Pz8^T`s7L@nJlGa`N%MQwU@3!kHFXY`-vUAUCul=`{# z(MtDh=7&cl+SZjLtlxuZn%&xkKV4MFBUhMBa)$2cp-5%iVE~F?USR(HCCOAyPFm7- zxiXG5g&d0ccjMc`CEN-%ag6QhMp4YmTQwR$L-o~OvHS7czkOfaYXM)HA|l0|Ved9tI4lNnI9%$?2? zo$v0LuNiGl2y9~}sARvt&-~q(ZNuK6*)(0R4>#T>FCezjCZu{VT@0T(4I@dXbZe{C zX2quXyq5FqyK&WvzW!PSoab6vXS75AZvK2@Q>oq`vbR(s0l^wa`p88ox_<`ekOU(YUm$)y^MZu%DB0 zlIg|ug1`cUbmC8*zMB%sVX=aC_HRQ9lddkb+8qc9zp1iyAHU1Q6#K9i&Mt`~ZO7;m z58HvUL&zvOA#<>L8D3Uw$X80%JOB&&tfoe_#Q0tN30UlXmWRE?jD*@EZ%lBWWhSu@dp)svMtePPva* z;cHC!;0MDK)>jN4wZoX&6PTFk-o2Z@v!vg}N&SK6IF2u+Sjd_FlUc0b*gPS#phmQ2 zb>r6T8+tw2BqN8CDu%vvpe;ZH?NL5;$S*!f7yh8Eh75EJ3Cn z>s)Yvmf9!$4MV5c@Iw1E%RYJ+YSaDM`4q)AAWC`cGT6~LT!?<@X+8X_ z5ev)MBl%E5Fuou;Ix zlQ>O{r_sUYUe=REa*@J*vXYXX2GwBF{p6|HOAlKfIF49|Mq zaM_0v-He*U_QQz_{@UKMR61&^Z@x=blW0gDMBd7*|DpM1Ng44PMz2`%v6AeyDF5{b zodjPlayGtTT`>wRF|_h5MoZmlX^yC7j=-S!IoUY~x>;K{e^zBTNw%V6j-KHI-{ zYmR>*9Z<`oM7uxQ1wP&}UG`i3WPZJ?O~s#sEsB}oLP}Y~(qV+0&&L$zcpZt4i_)aB z!HoWm3MG7k7dMK2wYV4xFzR2S=og}W?mlzwd1#OZJ^+DAt0E$749B!;9X26`{oJnF zc=6;J?3t$LPncqFHnz@)R2kzXlDN>U-tk3qh?P2L?;YbyFFQIJ?VM<@QWG*?95Cu8 zejprKTy3;*>L92+?lH~0T9RQ!yJGOzH$!`(vUyQEs7#Wh;#pwEw)QHi`5&pYo8Qul zzSK==Fl2)@4|EoJvAzsNFRtDFn9}9PoHdqCixYG8nxIP&E^nw$e;BfH$jgHX)^H*+ z@*p`5<)`vYi@&+AK`z^AV;V~?izwT8TME_~s@sy%$S@QW=j>6%8csj)L-9ZJ&vp!H ztWJ8x8lQX3!;4eMo}NUpoRB$IxClFl=#rAbYp2hqKN2y`s42g*1J+Tn;dj zALgNu6%K1>%;WufB}(lnRNsF$0*^0qZ{CvMA_#I9}RSjNeP^8tZ_+>ZC>$V z`FymdQ_u01TS$R$0K1gB>Q@NA;BVHz`Gq86O3<-z1C_Y8! z*z8=#{pNS(e(_}vGAfi7f06I=uaeF!Zy&T#2`4&S^8Ti4oXGOzm-q#uwiV?Wmf`zv z(orEe=mJv{s)Z&`pHG;#T>VwgdfzmTs&GC0=I-QfWNbPVD_fnq8B854d5fMWK@NxP z9A43EAP!N1ar)=EaDNnm(?yqa&y(kEL+K~g`NUg`86=J*bz8VtvOc~GA-+ZAXqT01 z^B`AOH)l_{)UZPW`y)>gA-VC*L^sdZ9(zBR#pm2ZZmY68FpaP1D#61vSB$$VrwHL; zf80+>ihMcxsHr(Sn7iUbL%m(%Rjc*#C8w&95-rIcnZr1Orlhi1RJ8=4G15ljX|ZWPEU$jC!ulz?x^0-zyF1?&5a!_ zpwT5Q_@)yvFA%<~Z;MRh=OCgoKX|-21~g8?q7BDHTkCW2RuMpkU6&WeR=lpscXSyy z{`)C_C9JiXJfk|RM+xzMLX7ZQ;cAzy#)+$TPrIe$Qo8kct^-&kQ&QxL0@Ffq*1f4pQE3ak?9)~|TwazQ@?Q6g9d}c;ah@>$@$f!7c)8?|R zH5;ic&GVl-jw1WM)}vR#u4lapeed7cXkp&2JK-PMuEyTMQa1N3fYeH|J%M+V`?wG= zr20N)VSWlU%AJC=FA14vL%piaD)!FfkXhLY>jJn~->sy$f9?@vB;9Sa8iB3xA*?5N zCrM&@Eogjp2pc25XyAFjh@5xxMA3;D`+x1 zUlq4V_&kMQiH!b*SdcU=RY=^@JE}!I7zXZ7*D5-s>9lDB3``6W2U&dh`Dx2x1p$&+ zXupTZl6G)ku883-yCIbGwp(xV|{Xn!1Ci6uhi zfOWnYQKCIJB9q~`uKrV7>WtvbsQGbt*!q4 z=}+r0@N2Uc>&2tf&^mivpRA5|g+Jv%KkESRNdf4cuNgf`66nH_G7(%Op(&a!IwW5u z^z~Qkjg7~-e-Ba!p2HuWZHb}rDhzz2*r8|1d4FQV6pzOhMh2}V48`H%zf4cUUo0;8 zaZu>Mm0~?)7$D2k9`;sv@zk1i_Aq**Jp7ARvg0MX(mEl~cIY;NiZP!0Ly-@o97D)p zmy2RPItS-zIN=oyrTfrtaF`Q{7QSUvES0{ooObhSyLc&U=^~L(l@+P1WN4iu_J9MU zg)NyPL-wv-wCHPc+2&$;Lsaptgdz=NF{7l%Cx%D)>I{r=q2az&<%qiY1VxZ^O1M?G zUNF0$5KFefFiMW=?_F_DP7aa+3Iyy}j6Wo5cPBgek5xkDshzjV_7hQ52!aXV65*hL zJs~k)nuMu|6ApiIlXLq=*8)D>l_aSJ12Q6R!3DWAqWAqS_dd7`z2P)uh{(-l~-G1vg35?2E))nd9ln@$m*6IsRVZcLq{(Ojd}@YsFRS zszI^u&L!0@sRc>8KS88z24j4MN_=-gDeysrN(eDTrbYgoLZADWvE3qH0F%3I~$`Vmh_ept1 zHGU=k#L*qb_U;c89PN~-^q?)jPMo)jOi{cQTVXqzf?vlNjJo}>`^=jHNUxGe*?74_ zCVfm}#!eASBJxd>YmD`cUeL@lj>v{4df&iS-S)XqG#;8V^j%vMmOoyM4R2uLF{}J5 zx)j=u;sgGZw61p8a?wJTznk2;E-JjxwoQ;WbF&noh<4XVZW<@bTp;T~k-xM9l8c}T zf8180pj2bAf39G1eD#;s+rlxW+t3=L-F2w zZmrJQ@xGNVH|^D0CigdAuQ<^btN(ns7?`DiH|MBr-;Yg z-WKdrAA(6P^p5r-sI;N*Q4+gg!e`gjrt#~E+>eO#bKhoL3?<08(+|SJ>HH4Ivv1TU z`=<0n_m{`mm^u;DNm<8{S5ZH(QfSTk_B}U*b2dcS%j|wjNFJy|g$gL8YCjuIGK-Fj z!${s9+iV$cQl*(3NDe2?fDJhk#I|}Mo|wv8WX9m_rqyVwgbV%LR#T%w{rb98qvjSw zRDG&kD@UDy&($I>5=X^OnPGN($>Mz8LlWOSuUqf9;z;iWi(pDHDH@mA&N-ZaydRKG zJ))LlaP4ggcD%qHal1xijxHYz4aM7!;f*i)yR`ynC z*S+6t^j32@Ue;p=lPWYbZGYqE?&0I+12~f>gCbS zRWaJEqke_8D6>c;@CIRDh=NR_!9z3NnZO|O?m2F`s3)BZI~;RPpJK1O(^pblKFRsJ zH}PeL+F~w+fzF5j`f!!dI#P;fsvcXe;aN;-e)mdl-)2AmKl}Y7(C>dcSU2_AwL^vB zOWj*f|IWXNpE^C&>UST`xb4i2E zGX&(X_F*!*EqNv@UG9nHSJXz_cI9Pl{WmvB^X?1tb(o&ZvXD!wrKO7067gs6W?Kmi zc+g{Q9Ua6XSgCc-G3Rx-n9}o!&n1=yWS&3f3ulxP$9Ao~ihcc;$XO3c$0&#&ci(sw zxte2tS18G=ihh5XP zHmW?Tm(R&3%iNtElrJB>T`cOkeWH==_0?^0*S&OSyY9gGmE)Mdp_8cSLb0#ZsWjHP z4rReHk!YRXHFXhF>y!Ql$xxEo2x4ddg{6awxLc-4z+5Wr=o+7tkj}qV6(y$d@qX?{ zd1K0XJi0Z#8t1Jayi_D-+5`t%n%(0NuY*Ws2eNshbUsfGz80pSD)renMBd5Ii{Jb? z=$s+Otq9#w!+n?> z(jT&W#$+D_f=7N(abTeJe-Gi<4ij#C;rmLEN*OUlS|zPK3t}YBpzs1L#$na6=Qc^+@@fMK+INH zis`TJTOEN+-0x}jV^}9UHZNAaZYn3}Bdw_mSqS=y12$oIJzwz#;(t68*`*tftl>ZX z^{h#hHOY1C8?*RFgQ2(8O`bDs&m!bU_#=(Of3=(!Kd6h(b?fc76sV&PW#)V``g5K( z{(QQFYV(uq9czoP2EUy-QBMeOWb=|mDDD^>KIF%+EB(dYOA|&U z>12_(lh~})HrhcWB^KNqJ3Yg>p*=(Cer_83p|ZcpXi^JJ?@iH-AK^53cLc4gc-#40 zo8rt0cRnVW9MgEvX2s9Md(US+9X630z78#gbtmR(&@!#ERtRg{1i?Kjcp4CTwCkxlYB_w2`ZCGQgR`lHR-JZ98g z%(3oFKsuQ)AQzA1PUSUBrSmKQG0#~e{YG((j!P@Z5L-iGpxuIN$T<>UfEvNATJn6Y za3RRxv#Wf88K}w6VoIQ(PJY>5{6(+QYIx#mmWV$jV%#8QGW93@OReYvQs2mw@eND=;-t$!zQDi{Hh2c=PLrV1;Qiuu1((L=(&dQ>+k=!i|j z6nG~;yb*V6CG}Iko3CJbRlc%}2A+b-s-M;SqHg53^-pBOSJ3!&eUxf1#Y=K{5Ee=Z z0RptvWk-}HzQ3^OT-D`9u=l(fr~UXf!1YW zsgOT`2uBu~y59Jnr=$HAkhHm#_D;8FK-j}G%E5DmeBW>8j`vQ_u0T#cB2O$3h)`JD zhjgm}E85DE>UH_`s{(T01%^}?SEsU`vzDqIjL}Aytt*9Q{37)=`WKq~V8RNk?|&7Y`Y)-a zus=n=Eo+coWdMLQ(dgBj82k*`e^dcp5XUid5Py?1U)ClySAu8m8MDnP|Eq|N>3}_q z%9|hf`Z5&52eJ~>?=Gnkk;;a`Cbx)I6GJ48|7Bi#{P);(sb2bcw}2aq!wf5mO((5R z=1cKUT#zenih2AV`xXJ09tViT!ZV9ZD3*hUFQUL(h45rubZeeEl3vAyXE7>!ZpnzU zAnmWY$raNngMK0Rufl;%;eU_nKh*b+6q^sMDEFgxALN;$nEru3vkPj)DT@%z+azFou%fDI{ICtUlLOXUs)#U53Hbz{$x0xz)WRJ>LHKTJ2;ulKp^9 zMr5|(#|c$^`w9KxxV-!J{*sx6u9cI8##$0^R&&kni$H|@0ReM0kr5=cfJHNGseC&e zU+UkOxa^VjpJQ`BFDjS>sO*l2q~WPV(gA?Er$9;k`gDWCX`HDEDL#|JDk&mT zJ_T=n4T1EwMW`I$d!H9?QGJ&@1afVEc{y^d@vWzuwa3mI(^IGVeGR)qiu*4xuY3fR zcHe@`F!IX|ZSvTDYN5d!VD|jxCil%YiU-blK_i1NFnA1@nf0%^uQz6nrh`F-ja1F} zdv^FBO_Xy8zJLhaajbS2bpv!lFjl%u68)TEtKw~sy&ayk*@nG8K(x>%yD2%_f{xm> zD@h7g&>f-lg^vyGC(L<{FAxVgjq&emuB4w5FbFdVku5?5J4Rqkk{7D!4)2MW6*Hr6 zo)&QN*)bSvS<6~LG2mDJG;>@yzgHHx2c?KW)gG1FF+w&IX4!__UJLno*>5nT@VLG(iNYzaCBCb-N-1iouLM5SR@&YrDfA(L03}V{r ztv1j;3g?g&fgJt2Z2K?ITNu1e}gnkRr69%65|2r6tsvsbPKGDIO&LF*OJF>_0Dj>Q+? zhqW}dz5P`W-OgqY9GMzK6_EIXz$ZQ6(Mlql!U(-IfvEStE8GCnN@90W0%qbS?b{D* zk~GXRIHUmE4_SZ*QO*fdKMm>)J_xq0+MuHW09>XvdX~TY8xW12obm&HzJgTZ9*M=h zo>#e$KEW=y^uO)`eXrjpm7kbGg{RX*bTj_hj!P4W?!&vndCT1Y8?mwsjF0pMKYVe7 z?sbaT2u{LW?t7e;5dn{1H?Wy(PFOxvBQoV;Abyu{-Zl0b>LHTM$5eEZ`C8*^U5o)a zgKEH~-b|EKe*r(&LeQTz0z`tqcKTUHI0bd=UDU8*{~K?RBDfKX+@E0cW{Z%j;`~1K zfKtXuSr^z!{fyIpYJrfVC_l94z_0u-gxNskc}yE**e5~sXk-g(DMUI|8a|gEK&yc@ zBcQD}|5^sL^_n%mFh!BVTMEPE(vV)o@IH0gLsv5Tq*H$*Z^)Z&g+Piy-sjD;s#)6GPs?6XhJ2)F>c4>QewqQS z-oIKz;Jo6{a`_$=Gx41XulD~W%Ysot+o!o8K8c8|Mko_N&GhAHqI2_-E=Yrx?Il=U z?Z5e}q9nu@xeM}NDSZ9WO28g(*mxKF`*Y+NEUr#>##{X-MPv~YM7VbfaRL-|UH^F^?%|5EeXx7#a70I&!2VuV z`sy9In7bJtUdg}9phy@hOwafbb=IPvIfVJz9W|-v0>iP2WG&p1|yzcq*zNiG`A8PTiOV;o_RFG+?n+|7r#Y zTdp(2*uW=bkjZbc1V7`x&&IZZkrO+5fO8Wpld6dstiI-$04Y+mNjBlQ`XVZu?9rTn zdPx$sZ#U_;KewFP{ccJyjG=(+=i+f}GhH_ZG9276RrM(0Y5GlqVQ51gf(OjrM84bB zoS?^^b>(Zb96D^doPcc+aq=u_be{>S>^rQPMT^!2Rs4*HsnVfTwhg3Q`7R*i^#1pX z?m29b%I$4t^cdHSXeHSQ!eQ>v|@pg7Y4 ztSFQ5r&5?0!Ie7d0n_`3P4`T^y~xUgZ3DWPd@zLQgGfz66%i24X4nLRn>I+`-DCU= zyyP{I)avr7ouh&kibvHoF~%n(5&3qL_n$VETL+ye7ZA{1K;eJS^qGF<1UeiKGd5YnF#^ z09R5W?#B&2C{mlwkD$Ks5U!gwU{rlRQTlQQO)@HpIeh|` z&h+u0{74H}9v#%7_yK5|Eu7F7{QZks)cDKqh~WxdW$yB#YrVqTRA$$Xq-2vwA3suc zS42)~J+qwg4_JN0#NC!jkHwdLu~eO{TV29s@Np8c;kXH=p`lCMRh%gN4Eb-seJjgR z&mD(z_NXh4OW~`DFc16>tw+Oo4{d4l=cJ=8yiSEp)^qZERLif82o3*mj-ESJ^cYMlunUF5rO zgLA1v#~Anq8zEv+L`u8sF0d%fnwZ$c!)b&7Z*y|=dk>EPID9$lT#bB3>T3@&!*Urw zH$lOXC#i7^UzV&{cMdJuns zUsZ|T@wPus?Co2wzEQA)>)VVjd(j1^dzK)bUlu?_P4z_BKnBZ~f@mm5y=%O}#_H0| zCuMJ+jI+0CavbcymS`<((xm+Y;oJ-yR=Lw_zm>pCz2*xKK4S~KHqDQ@^vy9}#ZC^4 zLCm}*#znOEIc5omBJjWfc%R7@k)(=m1kC#zS4Ijqrdv~g{DLDxavA%0B0rpQ*Umjl z>233%!_yZSS@@ot2)9k~)7c9GSDEWG6X6Fb`UiBL@((46I04s0tTdKGv*!Jc-G!_F zuJ*%2ahJ9il=*kVFFXO01nrlsih00#{9NBJm=x~wm~OM;$9v^lTmCLf{Y|~%r=D~- zpZu&ydNBg03zrc5*l`4HCxz*NFkNmpoeQbcP=<9bJPc*6l%RS z*S7xpn$%IpVZHTOiAI*vrTu3v;Kf|4;$Po1SNTI0?RX!yJzVQQGQGPx!f0QH>SZY7 z`)gxP@w|b{b?=nT4s@eGa4p?J;WPMPq43|3Mp1lukV=q%$N_c6a+p~S=du5f#f2Mp zu%29xya=eZ{Tc@Ccvr?tfz5i?)nEJc@ZYr`@ycC;6Q=VWkxR^g zPuKL;`4jnB#X6HxlA7E)9z)W6;FRK}GA13w8n zOj+`r9}ZE)N`zliu()s!IwtZ0LA>-*jX+T}S!cjV(Z<4wpWxlxE2 z`w6i@nK|j7D2lIG_IjX%@NJ*|%W0c9$XinA1q?DF>%-uK5hNgqqL?*DEDDA}Ujsb1 zyR1R=McR!`_X0LLhCNmF8CQS{%v&bF4LiO++ZgAHDE8MY1bmM6f%r$TKi4lM5Z)p@ z)H8U{7JFVw&}O_$`aH-D-mEmu_EP_#!uZR9mt+j?G5(CjNnAKOweZFMI(YoLsT~-E zL_WsUQ;*d}n_~mru91O%*9pw-W0*W~kFM*~;BcL9ZdD>Zhx__2gB(h`6U49g9Ly9R zyXPPIk$UT>MKW9X2rKHMz_T)IB15GC@wW2!GN`nDOLS*v6=?F@J5R?qjKO%L5^+Z7 zx=DMQ6#4vn&JvNZNW9RYv*N>O>8cM!!3A3l3I;}1e-iRBy)*1b6 z{Ga{wI1US2jx@3ZlmN`;J0dHA=c^5{i_inxD%f(?xs4z|UYa2EN z>nrcd8orqQQ*(_IEDs{zGz=FS*aaM`F5U0vL&Qm%KOiD-6PtDS;;*l}(my?zq4bgR zR`NC-%k|H8$A%y)hdGT?e#6@Y-w-hea#rVljs=%||7kVeS(-->_(4-h)rGb<^zSRh zLOo=~U{qVr2s$m?1MPtMNj)%dr+)Iy7msS|l7l`^4K`3T+b?;So4#SKw>&3dOY#M$ob=hh%TO=Dl9U2h08~uE#XAe!_-1)eFXF^ zX?_dSxE=JrOPvX&exgaTEhJ!DA{gKuL1V|)sf@JVad&H$gY}ZyU2A{@Zd?c8xPnjcX zgCLapfKNGEmN%Uoytg_oL+YDf{np>$-7%A(-L!Si<=|b9x|Hogo+aJl0@5R0ul>|M z>LIz2FNX)$PYg1ttW)Me5DleR7$K@{#lnWDV~9 zNnT#ufjh)iNBQ$A>AwLd2n}ocm>2vD30C{lp4ZaJd+|S3sUk+tQMkd~!PI3zOefeq zUTH_a9s9IW8`OG``DMg*w<+F5d!IkC1(~1SxMIJ1pZC)3AAxl{toyQf_C#cOgiJ(C zqcPfGNdiB*fGunl3AkfysooQaU773rq0lK5HoYH}>^t!N)flQWTqMM^f?fsLUil~R z_{5V&J?=N|FE*Azl>FDAKHO`kvn!&P!wn~{^uu_ev|k2v+}!%LUy(fGVjUZU!6`x zs+@vf{t&GGex@`$=Bfb*vqBydrHmPW3Gxitl~@{F3My zG2Y*A2`54r+1%EX$dMQNl5JFlz3L@q=ssU2rWvEH26eBugMg$ zpO=odN`C)-*=jZnCdSt|=hX9WP-<2WCuZcgkE(jFLUcep7LbZ@Iy%_WxfFqBBqEgm zz(lZnMP~8uT3wgIh-ak$Z@eJJKV|TrJ%d7d-Fb5Abm&h&jxXaT`R}<+#Xz(^>xDT= zX0B;N4lQ7?71+ITBdIIn{-Cr~b3yYP$pJ>j-1aZZsj+IPNK(qBzqkm+4iYL>(8xb* z204dE&klEH7k7pnoJ`S#l>IT>NC%9J>%(pM)=B zu&z;{YXQk)$@6ofpTIdT^5RdECOyb58?3>=tP~=b^trIzYQNrJ@MIGUJ&E~9asp?* zpVrhV2s%G@7d8+BZshLm(fj<#1CfULQ?1);Svlz#st2;yM@h-i@7ff9_1~m>id(z4YXQpc9y>E zL38G=sQqV_*Q3y3Fp0gUF0Yg2sZwG)C8|?l9iN@xABZVygs*9*eOL07TleYm`!qaN z-gL!>i+w2@oICZCkin}|dxH8(v}>8&Xi<}5RM$I@R_~R-R=wrhffOGgACbtVUpI=Q zSoV`t#pHR;BcEhK^ zmZQ3j<~Qqg-q0TL!utr>WN(6ig%LD{iazW zOe=r7YdQFM4%n@hrKUY4h$fI1WPhJR%Xy-vS)RfPr=rdFBfG)c5NeHoeXm(tBVD`P za{Z*Beoxy22-cO$`jMcvJlG(TpmiIhUMiM#rFZ9GPmI(!FVArodehyI@B|faimz)F zpIyp1AHgj&Rjw|bdMySVT3QjMf&FS{TxLWLXm@?;CVQV*^dr+{X#&{+A$xe<8DR8I z&D@l^=m0kHPx%Zb5m}ZUh~~TMEupxk0E}KC6YhfCHRX_=OwyUbZCI zo3hg;CtH(LK@FAfeR>kDqv(oBuV**n8vLG1PDaeSW2bqm@IG%DlwgasQbQ154SDK! zVu)#Y8C2zB^0vAQ`*H@Z0??_1`@xd?P0z0m%aG*js4kL_r(J`DlYs zA%@?Oa_X5VFh%?hmyvk3D}JSIJI^NkAyeU4!bYsyA6+d>y49*Ki~K9u?{Ol6O4!Fq zVsM9l>RUGOKE5HI)nU|#6`FQy zK_r$d%Zwl*PsM0WkVVzOLqY;4K3Fqt`BWy8l^M}0uT*=PBL{`>{57ny$$5B9#z-=H z3_)c50Hl3B1MHen(>;h!*%XXhzE5ZR1kZ; zH#e$AEJDt%mAZfq(szj?D2^s?l(W6wqq(1J(C{(WzrQ#^_6r^EP*C=RteR*1LwH;g zV=zoW40b221Q_#jVyN}qn#2yiotXfKt)zExOa1j|9m8kCzf8gAun0m+_4~siv#FPU z9B)fghr#F812X)4qqZ0Bd!oMz@E*>97Eb@PJ>RNcvblI007puy$*Pc6hE1sHqF0r}&4+fd*d$-|i+&#>2807CS zx6%|6`}p}`+Uw+fxQsdwQ?shYT}WD;_Alfj*-oa*GUW~sJIT3u9~dCUre-9!Dtq4% z*xVk0)W#Y2X@%#(yC16*br#hbuzSQKA^sVxo#dG*_SM_L6<-X3^!+=wi(nYWdQ?xQ zoKh~3v933B$Fe8khSPfFtzVz0JffCp2lo4s`aG11%dul5@8Mwm-L6!Nc#R7vL-PI^ z_QRVBr|14>^@46@!C=S=wlXXKW(*=ssi~4L%MzT2(Z#GbluFhLm~=T~MR}P@Fi!7R z*NEjYnATHHe7mZ30|!MYE3lmN`LW``pFXRBNg>5B$jK&fxGf1~oC_21N6=3;wN5W6 zezPojxE3jXVo$yyM$zx`yztBPEqE^(7S|6!pJ^uje2qbUqQ1wu|BJo%j;H#6|HmT= zC1sXmB{P()5*;h6BjngKl${l_DM@8SRCcx_BiS>Ok+LN#GBOWBMyTKQ#PNFf`h0)4 z@8{p|?e_ks8z<*E9_zZU`*pwWm&LG_!RshLa+~?7%TP(+42I99C5Ye6 z*GrAur+Tf`0m6gtC8sKuGt%N5+=I9dUb`0B`Q~OzgMC{!Qt>!YTcyM59*SgSsK3aF z1Vz{B@fItTQH*c=Xj^Fmn2T=KeD5@(VD@N{o3pi2nO>9l6zFB@eEiY6_yd5<9zwgn35A zR19KwyrBl1?{N~#VOoOCvaywPHB@5mt`oNfkPwE=KNb>~MLhn7BLut@oq1+TMbGXL z5)qU3#yIKjr9}5z<{2t|oQxfFlPg_b_yW39dPYO<2+vNP7>~Vh+adf{l$IJsdVLwE zZMh#crHp)umTqWU&zy_={oiu;&{in>a? z5DWiFjI#M@_1iDa3K73Sgk6B!Jm-wRP$@-$(b42fJff`&-qq`>sF!AYx~OB!6AfBw z01z7>D*2<|#cugyA6s?q2&g;S9im@sOOh))+$6#jnohVCR)4#9P_QTa)N73_9WyY% zZx*k0EGCat<o zf}CUjQFW|3*LN3JfbP?y6j)x=jU$rqU6;sKI;n#*THG{7hwtt$Nw<`1dHyRq^=3Y_ zO`J8soCSOG!Ba0qk>+MjYUK%N5v{sZ_sd?lY*3_Lhd1+Fn3$41FF^5-E?WCJ%EG9WISo4sijVSg% zLdMxB1sdVX$=@54>SSuBxJzjYp1j&L)=_)9_f25;gofyp69j(z>amDM`g>U~j5gkB z&g4E!EMY0Z+{RX_*Qwq}J!5#Lb`7@UNn@#3;_?7Qk7ETouVZ{Jf)nv%ei}M5+EcAp z=c2wni+fa~(<&N2#3HQC&> z^cMAu$F4mL-<}qxc&)*39qy0bhHE~faIOZaAGJ7N23VWiYQ9sP6qUHkFNsu^!~1-P zszfTQ)Suil9-Tb76S^_aUm_r+jN7*zPUp!F0c3W2&P}spw0yZ+Md>EW0fYlfxP6Q| z#KwL$@uO3?_o9=h8EFV7Vy9mdh7H}Tykuxc`1R@siW3()m-xJdFptgWNO46kx$kN5 zYM_Sq?nAR%IY_wOogEr|Ep-Uz`HiE}lS{*7@KDl$iRH}u8%!cKl^R&eKhx;HUVAB>QjY@bJ7Mn z%1Yrg5~iG|nN^AF+T@sU%~?NM8Cj}nJr1dly=3M%N=wvY*ji+FhWW)AV<|S*b}PSb ztz0ADJ`T1o`EzIpWe3vATL4;{T#i*e^)RxiUCqitvjOu`idYk!JawFyhipRKEa~Rw zIhU}B6-7x;4cGv(`7+OExk}gO>-V!&zrBnRA*&jtw~3eaKIM=}VjYgkb*qhkz#V48 zI`)3c`XKJ;R@gKUYX5HQ6JwfIwtRNupSCW%X(^#^i@@#8DPK^GL#Jo{G^3v)Gns-Iq0 zZcAJ;&T70>RZ?z{gOd67beaAMdg&`~;665;$R_^26rXpRUx?;jJJ+Xnu9}jEZE^g^?@G#s0OB?p5#r6*!w2P;7>Mg7j<(mju{#;unvtfk zd5}H!YJ<|Q_t_}qUmT6^Vq=$L){Uk?8(zp?Ag^6OPj|6GO(p(k%*5}F3R$t$>2)8O zZ;xvvcwK0%kygy>jA}n0VDrsM?nyFh{CqGcf?+duC7k$YJsTqHnP_J{%b>!SK1GYT zxMXf#@C%>oot6e`G4wgPf;|%Pg0iNxjg_n|&a+p&%&I~yU2el(Xr)wld-e-(_d54mZezqI^jCXBWN52XryJj1orMqcFWrwcQ|n0fkVJ=@knwF0dA^dE z{)wKFBOEeA`4R^!oISkFr5?=2tLPmQfpeUGxc=8B=3 zeC9`sxy42w%E{b%yK5s)rEO)3gesbipeA`!UME$ZX2huqu zZxsn1aJy26remA~|8gp9c9lfi1|0JWMg>_w5xzmQ&aSj-v$IY3W zmWk5B1Mwn(2cF+O+@R*G)pfFxNBxQEWP(ErQnh6`7|eFjG`_r1z$O6C$VlgcSE=C*U0%vaG7EINg}an8_>s#HaW#T5D{ z>k|k0?ZeTGNXPydsgLFsrDn9qVJ^s8GvG>ZLDZow_>bV?lRP~WqYeFdj)oG2w5eHO z@3lLojH5SI8nBkE^n@j+RaCZDZ^;HxtH}0pJ&YRi20Zg3`S!4y2Kp>R{`>Z;RPuJr zorIKFr1h3a25yC0A>SSLW$zh1g1{*D0zgip#>0^Q9~N9`Hl*_E=aUro_%nE&boM?{ ze5sfPu??`r7+FPFlic|-lGy7aegnYE*NP zATZKct=Jw(!*bs4rzN5&wZMi^gdM{767O?$F)<%OA3a*PiA~fH-G)K9l0&k zW5xf%=o$vyk%}Hj%cpoK{7`cdBeC%L4KCArNr*Qk#psG;WX+?YJ4N5!QxYUH6U{z6 z<(k#`^_xWa#oDP34^-lrdVbRHJf0g?e$|>wYNq}YZy9`0k)+h>;=^SCaz&DJ7vWKP z66=&b1dSRsKLoZ1TtER89_zN>^~}Bb6%o^h!yUhwqdsn6lY&HS3? zF?KZ)8mL4|5?+Fo^)9wb@Fws4fpBzS-`Fo5$Of_=4;t?tA6mFdmPW0zO54xFuJQax zlc5PU!*QHpqUUA5PlJy<#%7|qX*|Z{>=G}2Zda=&bql)s`uh7<01gO)cz;pa{pE{o zl&Y3wqWMpw2=@qq6R9SrWMHwNf4p|xEH0lea8?&{#QGr-Rb0x9PHy9G9Jnk~1pNao=8Y%pM%%A!l{y>%na)TgPg@NzKfk zM-%iwhnHh(j&|WoL#b02P)z|A$IkAo{8ACziA37|08`ke&FBbOaD;wD*ICU_s}SE6 zC+7IY_#r2eOni!_v&$S2w4Kf?=pocTL&Zu!FauZh3NiWsfODb7s z2*?nGMsd418Dd3S#eR5t3uPE&>y=2wzX1GztHu6xWZ8n`edW}L#Kgy@-bfMMdX>Xv z-d4e7I^T7XEI=`W+YIc5ewoAAhbI-Ba3s8QAEfYJlwc-R{=gI3=9qF;r8qJ4{hS30 zm5p9@%3Hq(PO<|CEGA48f7ax_koA{VeW_*o<2HM8?BwrXi08Ia_fL*|=ran3x8r!X z=d{~Y71nY|eB>P?hE&MBmAOUKGKb02fVWug)52I&Y+BS{!$5#MVm`IvF{&xknHjyZ zCfVYbJ#tf8z=*cg0&igG7xw05_otjs+eKF$Cwf%JsK|ZwXda}w)vm;Hhv|8eP0;-x z9YY5ciYf}bx(c1nOen;cRN@#d?r`G_DKoiQG`gWxyEL7{YNPR{c9X5e9TrdJxk?-0 zTEsoWO36}vje50$4u9u%vm(;Gz2K)%Rh91`g-rMa8HXPdy|Ulg^kV-#FxR&7YTEqn z0z^HsmB}t$R;-5cP~_IMSS~Y1^7(a?4~6hR5;xa|X?djvcn5r*nCgQkE;t>y%zG=< zNWu0;A%b~Vj2LdV#WA!+m+>^T-7EY>W36MWggNcDQuQZ_TTFdySR`x3J)7uR!2%7) z8U=TYT4ZDzT~m399;Hzus+WrXFuhov+Il&HaY`DJ*aOct7*F@c%T_;#&wutt*j)2k zZsJW2w~MCfr^-BThXwz{916UmgT2*usLdqNWHim4R2UO%?&wVH(1xZti+!xx@nzLh zYw2c{h&*J|Y#`-+#~=zENWg$r9!Z@wG#lYhO{w?|xrh}ghZ!|6y!IviFlVo>;Wh82 zQa|C$5a95L5(%g>Kbbn>{Ra&0u)M=nyun2+z+MTv+4y27h&o14@yM`=AcImS+V)n_ zfV2NHI;V}OOzJ7UkmVN;Y@dm=Q|k~LFCYpR&ix9K(g(yfz4n_k(b}YDS*5c|&2@Im zln%5+SCPFZb-Y4jg}%Z?v3$H;O${IjVa)ZJ%JtdV6Tf;_`{r;*<{*8ijNyth z%~5&j*SDVHG}->-nWZ+bKa1qLYfI+aQ8+vtN0LEV`GaUx>RA=phry~553tG}x^Hd- z9X~k`K;h=(<*8g{WwJU1hFjWGZE)#vXSRNyzr~M>Eo6%?)T5er}igcmZU^R zosMpO5C0EI2ofLcKdE#1DtcXrcrRrRbw*p5DZ z%5|-7>Cu0DQ4Cy$uM1OygnZUP(Tb@#sCSd2$VC+m^LK2?#5Pi{Oe9B=v!*5aZMKFe zBmUzCUdVLy(_eLi1U!B>AU_>WgJ+6SVc316(RBDA_|WMAsqJOp`-jvThUjjOuCTfw^v4tYS*4z#hIElRRWa9NT4wxi-#Xw&S+J%HJCw!FKLU@q#1P=@+~N0+ zI0gc!fK+qi0OXC9SM6NzPk_Qgu0f6W2dU2Ud8v*KgA}JK!HWLu)>5K-#V0_?+zou4pzINC&@uPTzz~ zmXZK}Jwlo~081m!D707>?=>>}!`N}vj&<%lO{f6@x^)g1AhU_^rnvx_c6(kyUEgII zED;99kNio$G|7Q(3A2%&J<%_b$oRIzo{#4zp5Ks)<9kiBFg zTXiZuN|2=SOA2}Dn`fh=gM|pA5Y!V+d7jP_XTAdj(;3L&6#~t+|B(1nYx26C6o7oz zFn=EznP!2qxV=o|3*eSm|MQo2=JN~aup6IJip&QtutW+y;_14xcCAonzRRf0HoScL ztGHaDna4v`X4OB#INjDupDA^JNQCOTe{yYUu?NPRAdH6WNHYe%AWxOvoo1PS$hfrGBug1mP49o&n!Dpu#o}BXQi{mXtQ9@R| zfaOj_YN>%5Et1wlApL_VD0A zuR&d``^=Zu&XA`p_L+jDVjj@1L>*#f>g4#_Z_EK=9}lbgllFv{AUi_ML;Q)C;_}0| zJ`nsLTRxZp$GZ5hiKNu=QCOZuNaScqp>H^nitfIPW(W8x1P#nhEGr-+@4Vd@WJCzWR*)|G`hm@w~C@*wlCVnF6 zVMzX4H-??EJ%299H;yi-qE0I(i3Ro;l4btvYA3S0$4X(3wevRr+@3}fUmx}d1yS7L=33CpN0C6DA8L01-(qVgOHsa;Y2t!e8l64^^KOeSdMa)^d!+(-h9xB@4?*V(@!7`*HonxADY&zG9te)E5h6a< zhE^s!<#BBg$z#zy9(Bp7M)#WFB!@pyzv4U^XiI;$#Buzx9~N4Sm)(^7hJIyoo1fl0 zh-Qny3R~|@-QBY7uo}*>c3z+o*xF}r?@koS7C&3$MRuSm`U?}%QDa~oYo{XX-wQ0d zezCQ#4YmRa4WA#7wJs$KLOQfM@m-Q+I(R1W7(#+rN6J8C@_Qi}W(`SV=LnkWKxa>M z7YX)yTdfw*ss{KAkmQ;3pr^~q8bT$y(Ht}B_u$D%xn9U>eP70^L_Der8Is^wBhRD& zki`FBh1*YuuKh@x4=zr%L3NAyg(m*8Yp=B5)E&Aw|5iR8Q}A+XFQ!ZnR#g)2#Yj(7V$6>4Fe^BDs1v+g*Kh}-W54GgSby2vW1#%v5+B0BtdM`JZ3Ln8 zmVq+z4Al47uOzvvcG!FU2^|R*YYvLWT3>#A^c(kiL1lz>%;o|4%C?fRj1QsUBbV80 z{tWGfUN*0wtLv=E`E(scbdzhN=6YoV4rw?O&%sPuZRp{2mIp~3JA5g&*gat_;7n0S z8kN~ui_8S*4H3)E24izI!*BAr`i`H3_!3BzkAj~aK10UxA(Hp_oxn)UR9jNO0cG_Q zDk_g|Qc&_bjy_vxFUfz}t}KQdRnBzRJdv8+R>`gcu9_eW)adiQY3U<>ddtI5! zoWYRznvaa}ljUg@qme#nNqJcNbEEF<+5Vn=hZrV=$-CRHR*S2t2dV&t>f#FhL3y~W zpgZ6c3o|W8PK2^ukbqt2*sUI#lzVxF&RXZYqEhI4s!+DxA|q5gLNvq`_OU+GU@nA_ zE4>uLbm=STMKKhWM`6u7)gzLyRD(;_<_ujSu6rqYEszA`=wwcF&h*Ckr9ZC&7~LBD zTKd6GGLi22$%-zwTsw=YJRCZ|a+2@`7p&12BQ^TFne9nRW@d$Fg5BH8n3;_bDjsry zxmW5kYo(nwL3@xmU+@Wgi6_bdSJ2%H_9ABd)NfY|5j1J0Am*;43Qb>-qM@%&y<~O% z3?>^PRoI51YSY;JE>Vxtd=lya556M%WhzHj6Ag^{xDmOC|`93OtB%JSq@td zAy`bey|WxpL$BU6=LJ%6l!_nAzo_A~4vEu~>$2vH4*sVkm;((G<#W&gNi^neNReG? z%O>?kZ)93nIR&*4y4Y&&$wn{I zpvL1a4cqIQ_z9TQA8j|55Kd6v)f+&k*6nWwmkuFS`|o3e8c4?)&jb6|Uf1znVE$1Z zDa99L=F~AxyP?2K)FnjsPekI`K=>W78T2!s{RkT6@iOhCzTUxjtZMtH7Di8$fv)D zoxL7n&J+fI`qD3%^+Ex$=J>0(!=X4xJ#!2YgQqr1;#`N?$g>&z}%1IUGaVN>9$j$Z@6_UN-W?Q3B)$N_^Pv)u8}>pbtvU&AzRtT4d zjbs&l%>#$4c+qn29e-+Kz}Y5_VveVC%c7N(GZs)Q#|}R7qY>`?GXn0QL%kK{RpTow zvjYS1W3NsXQ<5V`48n5r7u9ii<+b_CHm?f!YqN|Szv+L8ZXGo=!nSPyK-I`9`DR%3 z@DPi@;&k+86tzpR2zM?p-N#0jwY1TY1|G}o3r?u7uxNQI;rZ)1R-=EO>qV$eeX=qo0HKg96VqsJ(*vg3URXWr}RhX%k5{o z5aRa$dW)8>ch$N8Szdpv4rGZ&C0w8yqqNbE>G!CJN41Ykz4^OYFa(3RtVFWo1>pV3 zVg=Q2x8@p4)5WAiUhPgH1TsfQfVlpP z<#o3lVh0&1+UStZh)65ECs(Z&l;&o;>!vMV3$K5sR3l@8PBM&lvVuD*Ss={8(R#KT z&;iiHsiK9z-!tuN1y3VyO?8B_NEfC{wdEOLE8D!lY)xmdN`8AlaU+sf_dAxVQ`q{8 z5rj7%7wN0!lCL}uU zq#^I{9410c=ld1k4lq2{K+twFTxc->M=c9sUpjuhLUYxNRM49dXPkJJBu?6}qhhM( zr{v>0MH(qy-9o2sb@Rlx2;u()P-DWhN2&*Q#%K@WBr<44qTO2@XB4vxVbm2QO7#Rh zKie`9V7Wrrx@mew1Esf@VY2&xN@6_rA)*gnj-vObl;k~dxs2l)&U>N~KZHXPd4L$A zHkox3^LCH=6D}WDes}Iim*+qc1-E+o5cW#qic?Qy^Qj9G2umw^RP$62AqOw{_h-3^ zOA=}WBD}__tq?l?c2F`RUOB0-WI%P^3t;7C`0R4r5#qB*#DjD)hRWYA8Rhq{!PM*n zQzOXiNV+UiXeuQL3`PNa0C&=nvH_-wKGf3dp=a0rj z{z?2_L>?$+bvRr~F70C^jA`zu1(_tCjl}qkQVPBRGF9@~wR6Je!O zo;^>4_ahGTA|+ev(5>v1=lZh7n;U2!2X!MjJA|P*ZM3+1;iL)X5mJ*BC2TXb9Q6`; zHH7f`z1U83_na(+M{Ga!={6p+*}0p5BABzJ`*)*vw^chItQltqz6XEfE}^9GMla5g z_3y<$mWmU6B73tX0v~|yT=Jock54kmj}k%rABGaZA09hHf)Mq07b5(G`h^5~x0-42 z7x29T4rF6;+mOM(p0b@Co=Mn}?;ie{Op{@ZE+0A|;*bAi`@*4?Fq-NI9KCiY;tjGq z+&zlL@Sp6UMAQ&=CX+-Wcb%adBLqO9^buQG5lGL`0u4%ux=Z)%z`;F4~PrisXN$1sO;l& zZ$H7mM(HVB>m`Hp1*lNmzMu@OOxL6Mx7zt@DF`koKNzQV;BUHzfV2hwjh#h=`XA4c z;CJul_WbVNl==VN`~CI0_cVRT!sdg;#dp!}e~lO%L;1+io^ZPm{MR@C+H$VQ)QK%G zG3*Y|&VIN@jMyuH`O1Lo{sDTWj^8tEIe@>%zp=F)l9w+V=_{Rr4~$-`#!z{Xtq(;* z3Kw|NB!{X9@9rbdLkiG!^39E*jM0x6f5QeHSnID&T(JH75gu$Ea?`y9nMe(lFf=lL zepnt0D@yG=R9tMY0_vCW`;lSC-FSWL*`Z_@Do6?;bm~F+L@6tw!mj8ykHL$&LP7EN zpJ>7wb1-9@-2a~o`<{joq_?+98|0WgO*1uff<;XoAk&N*;~p3ct6w_sCsgz; z0HgH)#VfwmD$L$aRbO)@uAu-rTi;p-!0Q!2$o98&>!*<#t>X7T)`;0Icqfj~c7L)v zjK5zfE4(#zwSx|RuWdhy~BCR^$I2VW!T-lCvQZ)fnO->#bEj)1#dZr+5XQSl-IQuCG1ORYpfh}H|(pc%rw zN8pZGgAofrjWa*RigjlLZ%J%C#&My16xhbEfyCPjHpzB#eZ|Fax-%m@3(70pmS-$V zDUJc9^gYx)^NT_GOcn^67Xrt<*3rV{BOs?Y^4u0biP$v9oY$yuyA5@sFMy`mjAI(< zgX}e8;hJX=%CWmoIdtNk6%w-g?6d+E^e#mfZn<2!P<^(422h-!;wgHE4oYhTdZ%6a z1;nBrz&_29Y6!zVvuFDK1-CaKD8@<99h66U2O{2J1!Niv9mW*Jp!g&Ud<%r(Uypl> zkKBAvs$dkTBQADt(NuTVI8-gNCHQ;;?Cfz24g|^1p`AERGEYK*7g89*x2Kh@_|}CL zaD{NcMctOnK_CD#Y6bOXE)l0M801`Q5P)90fHW`md#(w4|Mn0Y4V6Z?=X_4{=K#Qsam;7v?v!HEQ6?hE@LQ~8S zb+nR&PLpbxYAFv7m?lQgS}f1>I0KGN=WL;yXBrp_aObSY>t3Wp1#t-tk^0(KQvPJK zBhnP&oujk^NCC4H7n2(lFfcg-qoM5gdrLA!7&7rb==98Ls9|>-3Qpc%G?08ucntG& z)rZY3#*bg!)fqV5bW?8$@CP$W6|4(&ibVJ2?UQ6@pQ|w^xNKyyo;(H#`YF%b??FTY zsk=)x%T-4YJzFe=Dmv@=q3UdosMQq$B=s?HuM~k6(b&wX1f>2MakY>hrV<+Npm@?R zKD|M92%#P~%wa#PfFFJ{`i!w(W+{}TFzFEo)G!nv@DAoFb!VY4bK7~EcFr$7kN`!~ zT!bB3dB7_1Zn9Rl<{$MFN!;Yb6iCe@6n%X}`fy#tG(lBoe-l!I(aZ!~wl!F7(SAHZ z?*aRtK??ipDUP0bi{$VnAURrwi`2EXXR3nAA!opU#JXX7@Z`w_#ZeJUTAsV{F&An% zOGA(rsVjaI$ zB4ld`JGTokY>o6Wme7wrqwp?;P-ZpoQ>p6+R(%!vq768xRAwX&L#oEfyVGsHE?Od_t-0r6p3sW8s6hX_i-~4*Ro0@q*!~3T7{YVDd^jgZ@fM z${Qeoz4FTVC97n?<wMbzHLgQ_L`t{tfQ%W~#SBONmD}2D5)q=0{tz>sgZk_86 zSD0TA1e$M*mKZ)%bPqO=7VGA^B1WKnE6Ll-I-4f3d7aZQOUU+DnP6|F&}K$GArB%ZG=A zF#4QJjX7&5X_og;d{HF&3V2T0^q1G~l*5X+1LQRaVB|qD)kF0rq#mwWS@KB~VYpFf zQcK2yTvI{@l7_(ReY!kZ@`Z)5LnU)=ldiQd-wF@36!8nln*i zM4s~MekNu|a!E*oMca$8`o@wV2Uzz0DA(b(x}m5I!*bUjwCfLR$>gu2o2P4g1@)U0 zGAzO!Y^>u9`+9w3nq0YT>MjS!fr)`0HyLuDE9qh5TH{u6~FyA`bM(r&G-3 z;tVT`s-YyN>P@dq2yK|7O8U`}XQcB%3Ekvxv6kZ(mBZX-e?j64b;IgZgx1Oj5zbwk zr~v-T3qkt+1Ncw2T;5P3U(@|;Is%GK5*7m9fJbCYp)e#mA0{EcWFKvxsUxFgbplwO zx>r5|4)5-4{^yBNM@fN9I0dm2Id4&lA(<%BZ1*`0TWNEDiDOY~`{gl6UpcQ}*Ll)u zs6&?^IR|Y1!spTX_1NLo+xZmrh`JP3N!={WFg#_@T)bFad+7(f!SUMt0L)^}>FB(I zMA7V6Z?{ZzhEAN*PMCdQ&L`TEL?>Y;EO(Uy{<#Or^|u<#m7LxCy7x(C6a$G zcpPbvlPgR06DbM5yWnnCi)C))-_v$pUHscz4zoSeRtMGD%DcEQCBD}nuJHb#{B>UG zlx|CY3SPOC?n%Z4tA%TWe(U+vPVv<~Y@dI#_tua4>^)?cwhHnfNXv0&C66&pGpc^+ z%kK9fGyM*%0@I3HP!6}RAG+xV0{Zc(t3H=Sg~iR8WaFz|Gw1+Os5q-s}h~H89GejkHCzoNlJa{;n+kPL#nm#D7?t%=^ErfJb z=^k2(bryvhEo-Q_vwd;aX2C#343d!pUM$$cqoQ_3qbfKKwM-4(?iK{C<31$BYm?Gq z5qK8&j?$X!uzFV_`(2>LkGoI8Pz~6;&EC>~fvm~XaV^Nlt(yX)s&99WM+ z==IsGZl8DkJ(*@~A~%f??-~Z$tY55OKdcb1S6~?d+6vXUYl-%)sVihCKiu#m{z537 zypZ=poIpXRDz@b4&hkx$z?aRxIEEhY>TV4QCz)>zAtid%D(8^R!|bU**QiLfc1E_! zNd_{CPLDmqRm2|rQXS49`lOtVprm7%VCdRn_(zgIJ6 zVg#V$GO{4QsG9x$N62W^Z-cAk{#^KRw*eO-FU2236DkvL=$>v~!o_=wrsey5Z?rV# zxMkaV1X50SL=%Tge9Lh(G~MAwwI6XI$la85TXneGG-TUJ(_AD|>I?u&TxKWJ6vTCA z;dp^npdjuWY%nnx}HULiu2LZNNDqwjUn#h*rziiZ;Cq z2bS<-%9X$I12$$As26PXu9CaYjT*M-ZIMDC8w7z0cg{RHm4LcA@>W>J1w z31pPKHe;D0)UFJW>a-oXg&wjB|R9RZvAf)s}uVjAul!lI`ExBqF<^}F9tzs`QIvei)EdA44U=2j3*9c?D7z;zdX%{)=tW}+fsnJ&pY7bFVjF6jL3u3MIgL`_m>c3 z#?y*$M@=zalD=44#IaDOyM;BRtWN%+!L zAWPyFRFh@&APGW)B%hOCsP|+rNtiAeh7TZqC2*jP;zn-AU==-DN2|U-ePJ0O!2N1^ zmZbq!*(G)UqQGys9_L7OSCCf@pv3}LQPEwx5+@}DM6AZ+iT^P!$O4ii;H4=5RUi>k z?=QvyB~v7W>Wwe~`@~jtij>6<)^69_DIabzG}_WjOy>cF;2sy z@%RoI5M)1$K#Iu)>?>p253&;;vS`+B&DI~2Y4K4t@rWD^&O{AZpBK|epzwiJsmUxu zMF4w3;KtAgkW`i!Q7rZ z?)-fkA8w#0 zz*YQ!KDH&Eg1~Qi`g#5H{dpjC-UXqcuQ->m?9@#RBJj9OJ~>cUgv)(*XtETa$A!B& zAl#s|fM?CS5A?5DgI`gnk&281f5o?Zd{xL0 z5JbRwOUVqJ1xRKZL7blhq@*6KMwTT~goO}`)5<6CelPQ34X`4)!526G#=)SZ!ngrU z;4Prn7XkyYcVlf42RMa-MGN4`mol%V1tY;MBKOw|Nk*LAo$9Fkm;hrp~AR z1B=S1g7p;;I=@>+vTagP?T7-QITibrTm(o4?)eO$av^%s1oaT7SOFv9j6Ze23Fqe+ z(CV99IehUOl*{A5nJYxZ8E$-vGDD~tkSpeJXaH>vYY4MSV?mJ?!6$WrJ9^FSQp`VT z1EXm`^$VMWH2>w5Bx|7}Sd-*J3SXPl_ktJ+O^FWB74;%6BXWum7;7ZT1X2$wRHyxc zc|pXT5J>e4RL+55>zyZb>55~12l#SC!Tk9O?+h^ zFE~xSjXQNr+^-*>a=RxBGk*Fc`V#)G+iu}O+SWV_2xL3}7P3j|!-EB&FQfwu@)@|| zm9;$Wj6`ifeGx`5V5eqe%AM*z9Dror1A58P1)GWybmO~|*N*V^lx?#AojR2>YD<&_ z3g}7?NV1-c@BV!Po7c+7Skk$bgcuIwK&s!L*diVy2>V(CF8?tlyM2e@1=%Man2o&a z&x;lQasffG6WMnF>lakt-7j|mHlCO!Mim0={wZtqTlsR0ke*+-Cxys#+76UzBb1oZ zIj3FLcmo@3t1I;6v%3kTEys++7X=lXI9Me4unS7Q8h{p@^GJ}$eOm~8t8N1X*9XgL zmLY%XGwcjwD3#GAi6boC4N9piU;(#<5V-v93*M6&0-5ll*@3b?pp5BzLo{WEq(4qT zH_w+7-d?dWm%J-8Q;&%`zY>#)E?EI6$wqNo$8PJf_07$z)1Y0y*L<+Df*e#0ppK$!-jD6|IHdJ*5P?t9=8-WYli3*Egs}46c10TXQwzA(k< z+Ri)=1o1vt__ug8v-9Bq=>Zn9ky%qD`dvf`5q>FyF!cYaa!5Yy zZ#MQNa8@nKBs$tpUu^E047WS?5c7UuM7r6hZmVCx12_TdS$gj2VTRyZ>YzSb&AFpEBT| z)wuQ3h9;`X(n8vqH}fYVtb9I?!Mgpu&=Lga78CBr z=dpJ_RwV>VZ_&4R0cGSp08`b)ojfLV;f7M=>DS@P7aDE)vtQE-TWj4PU@6+If!dNq zy%DtEW%rYwFNvAUkfWsH6``vc*DK$_uy5{_Z~^Z6)Jz)2HLSep!?O9%c7W zQsM!ORE^2*!0o(Yj*swf+tvhMkg9E*tynQA13CP*4}=6k7}y`LARQ=d_4zcSSxDP2 zRJ40GCnJoTim!URjGL{;YvA@h87x}!y*aiij#gX9@=t|!)y>Csc92Kb{<8N!=u-#{ z#nSV<7=HM-_FiZKA!1>`1nGh=FazB4kG)>^e%8SN8D0}jU13OfsC|QNynV^vb zq?e=rTAaEhkD<|sPLVB6B4=tZI=>_Dsu2Q21quoUZD||kQ1ze1-}(P1>90!+B|#V&w-`cfj%T@ zUzD)|Wl}y$hseyW3+&74e!(H#RL$6a3O@?W2ytpPOO*02|F}Wh7wo4F@D^>rdtHV- z+6%Te=8Y%0W4&NuTAUj;f>X5ye6$+?7alyf-*X4}@eM^DXoDbO{jUe|A-do^V-BkK zg#cpqOtd9QHX0KI{XkmFKc@g~?*Mnj?%j9tLwrQMT<6Yvh8{SrSKMmZosFaZ-V%Q+ zfWbk&LDr#coqhzcxMF2XeoCBz)UJW5;eJ z@ZL4QiCVq!Q-i!aN!D8c+Oy!W1$quUuyo>(BDj27}d$OI$z5EsWb;yYHZLO~Xr)%;w6T&;M zd=CSs$g7iAcQ-V$M3|Qdfozle{t;c@BmPSBn{jt;Oo-QzB)o>Au#`z{t$cIUqofmf z@9lpgk;o;=w_>kYhYwsD1(uj~$icrmAlWi-ZXW6@qT&J}iebU$#z^iV@tdGDS`L4z zY^_r$QO&pM`wUHaETADyJ~)4-)SrO#GXpyIA6DWJ`E-Q!f}wx}{4{Mn;?)C!`fa%z zF)h8nAQ*}-8N(t~rDZ^)Xm=j4sR)hph4Fk57YH40tfwbk#qkp#Dg?)v_Br+b-L&iW zdC1@kaY`|H320UDBZLtoB0JBa+^!AnQ0WoM(a429phe68fx5}j8djGLq(YoQB?_V0 z9PPdnX5V_Q<)R6k?60B2K@)_q0j4nmyyLCs?2uOPpz&#hbgzv#tu4UYJ;sWkNt7zA zFY2EpcAo#aPEgFB2$6sdJez30dq8eVTbuGXMCc1t_JfGXG(;t_a0c8>pzLGoKiFTar3 z_35@=jldTe@I4?`IVb!*8!C6|dO*f!^*GXP7zdZN^WK1Pq#raqKw=YaR+Obz(w-k5 z==a>U7o?Q?zp^j%f#_+Gt3n~)>y+RL?baK(DS#t3X({&C?`}qI#C!ZHEx#Xs+H9|; zRbn8i3yFdj=p1%6aKNWwo3G<*a`>TvbQ=f9V`46$H_H%wtk<1Z*PVE?s{&%ksQmH4Sv z=;a@XeoEec0}@PNzpfVz8}QEGK<&g>UjU5PS87v8$6auME?*|BN_XD7y^LH@qiJ&P zv$+WzJCMWLxn*cjqXVMy9oJtpK-Z6@xxqomt6hhjsQhTkpYE>}^^@we zf0+JION+%`evbR1OfJn9@G)6R_l3>5=6cpBu0$%9fYGDTQK9DhM&{p|#7LzoP>ZFp z(mS88eSWJk<9?`IbI=B3{Pc>}iph(!AD^$sX|I$Tc3-kxsrX!wJ$)yu>icGKHU+d| z*f~hOAj}?q)>N19_gEaKN(aa%IXSrjBDNGjBSbz2hRo&BS+wYdZZT zCznqMpD%r}qkRn)`V!=ke!P=4X~1vYZQ~q75+iCag|t)R(ksr55p!wYn6AA+A)hTG zA&$iYWkFLf@;vhzY^zbQ7sB;&lZOt8>wLs&CW=hW>BM*doide){IXze>&P2+7xx54LiC+Avf`X_8h)8ad$_t8GbYUY(Qo-;pKq-Vgd^<-Zg>YSeI&Wh3 z*iX?r3&}UVh=rLMcm4fU$R5?!zjfMHyxy4$5>e>oc1zIe^A&nQb30ja^0db-^a0|` zje4j}OXAvqLnkb!9%}JAq8x_a5`c^A{;>rdhj1vHXK8v0t|~Vib7u!3U~QhE<}$1- z=NkPDoT(2GguUrc@|;{THa5N?7wEaaF@VX76C5(36?4&~+v0wA@5Ww^HMa6QA7$X0|@vkT(LV-*=J>ki?aS8;pN>VwkxvaMrf9^?H5q ziPZE`ug`^$k+*<~^0k=r%x!OzpfA?_@9(VEXn{)W_q39|fO z3Z0!X_M`H3P)pXYx5+t@Sa!mx`)=V!f<^!?#$eFSRZSjx3PCh)Q$7SEJOJdJ8?<6^ zA=jO)`s20ur?^;@s!lJ*j4pJ-6RKE*#9U&&((>t<2OsZnImN}*IQh=Z1HaTzaeyT) z)YgB(f3P)=K7UBm(^Vu7FsB==22L8mTtF)I0TJ%fa@q;?+|oCOk>}{qjvg??x^;Bi)(kFnDOHe-Ck~u zSLV(Y{ch7)-MBPn{x4-NImg~GDingyvL6Fk%Py|#0dDlL<8?;-PTzh=>T(}Ug1(DF z3`qJ8?RN?~GAhX2*wAoBw&k|w16squ0;^|}LXN2G6qf|(bc^nvHTyvA_%u?ug8kyf z{%f@o0s?A)lf5vjrS}0WK$6j8UeeL&1!(>bJcU9bB^8y>D{&XK4L}OtG3Wki`a;o) zNwGvROU};OrHJeJfeY0HcetF)0WYzzZ8?Va69Q5g22r^JjEl_Py&Od``#{96>q_Ffy zE|}p7FzBKNfm_H1GQ=f`^6qYKrE6$u26Ej(o4y;rn5EZfILpRQ#C@fGEX6E?rafO~rkh`(#E z$}Z;@1`2O_LWF-qvk_paWcQWX@0@%#pR=j0_A3D90z9}Sm-R!m12zj#K^Qe~O4;UD zusJVf>>&6#Bb~+qe0YzAxdpt8JM77S$6{~S2;GP}fvR8%v{nIN0?Sc+VGPx~l|X;l zJm?7sO4K+`b`gd_0s@WjH?G~4iT+H~hLWB3n+QH)<96lQKK|bOei55fP4j(>T> z&_#Vh)xho5Ghy+_X>hvE#J@Qpzf=VD$u7rOT3;qnyP=<{8b(#q!AHf(5G4@-(}qyO zwOA+Bs9g*0GRwFEX0+K)Nk?t-Xh9BP3;BySW4f>gnY>>0>G;g{1sk+ z;949)yaoZFYiZNfIJ^2$On#{jm@@-J0vaesI9`$S^JKT=;1f9W6~JJzNl1KsVq&U- z-|9Xhu=mSf$}{0V*xvi^hyi6{2bF@1(}tCLNSWz;+b>(!XZ(h=omP9tIr^q6gUNt?%8x{{fOY-^+Yyjaq&M=UexvB_<}aadBnrk*i7Cwa$Zp zMy-B+_Adn+ITG`i^N>7JaqT;j`E5qh+X)#0^wOKq(<#<16`+$%3 znRS&{QVNHY`OUiTeUvjHEV+o>+%p*fzD?ZR1yP3}aYR{^eR^>hm)W|GHK7jO2*j9S zadCWbGx$2fs|9~ZM@mXc=2FM*&5Gc`i*K8~_&XP4M8yf}H!awN>jKVg++ew3xI_a4G2U(`>A;E&i`y&gJHhZ9i+O8?QUJ9gmUOXmYzNF(tC#2 zG_6f7quVn|!U;#AkB%+-t|%aK82WOfo-CvE*ZNzcS#c#IBZqe0dHyQA!XG`6%ez%c zTSw=pVge999cI3qRk(VUZu5#XvzRX=lrW0V_;^`ZSddZKan5~o;XV3G^FD5OoWQ?K zE-9=&MID`}C;!BJq_FcVhy2OU5lZLSQcPywj)Q55ZE|EXU4#w zAfbIw2J!@Q6#Mh#zBf*bAyDaJ-2Y}(fz*09^!-!^x;r1(O$zg9$dhXS{t@DRbgy%# zsVB|S2@42@Wn^U))YQ~aRXkVlWnTTraWebU%o+dE+dK2f#Mg>4XyK+mJUl!KPTE0p z8p1Fu{;JB8fV2tBeT2l?-e3C03p?K$0=5mMOAh~=zgNrSx8TK^f$JmSI&TE6FP%`C zheP$;*Y+b8ram|VJmp3psuBKgt^};#F^G{PAc|4Ae*Jo72Dqupe0s&qE(5nUgX8Zk z&uBYdwX?H}bVp>_&O1$nKIK%EvITVxhLt;$?WefZ(?d(XJ_GM!6S$cXKt})W>T$C< z=1HRLBgJnu{*D*pA3r{L9-L46V$dgX@Gw;70~wwq1m!6|2W{n=8+rA15!zKiVge2M zj^Euqm$%lq1IIitUXj*y+_$GXpJeG4eV_f#py#=^U>5lJF*W%arxi(9I3Gdax>LMO z(k1dA-W1XRr{sNy9yzeBJz?{tL9%i zpc>o)I_o%1f5wshc>0t&FgRG4;9+*sSEPAplvR62M+)qy^I9)s4AnqIqo>d&nyWS2 zzxV&Xk#@rl9N-Kbza> zFW%6{z`A||`a?AV&dG=9LX6XAwyt*BwxcBXQrRr~Bm+Rj{^}?=3Zy0iBGWG99KeDd zUn{v9FCkz*Tx(q~0%?{ut5>PUA+w`xYX(^sI12J0KR>^ZEeN+@1NtNg#tW6V(X(hQ z%AykI9?afrJHcJjvo`Yc*~3G-7KRC+kJY2kXLmo?I(*U)r?z^=CpUobD}A&$bDPBN zm0CbvkeFxzaO&qR2#Nnr%;p3;5A*9fRp33pd6xt+UOUvdLjWFhacwmG`#7MQitJaO zGXwA$2|!^Zgd2!u-ALV>XeP74J8Uhb=S)CY74nZE&*z|}8FT5ITFX1+@`BIXV$bzD z?SJqYmkt`BGXrIspa6RQ>QGX>t^d7y&qs>j$g|1TJb5B|_6s;3KBTgsm}KMC5z7G< zUnCv}zN6vZxaZH0I#$gpw~dsPcRBS#RFpXPa5fbRl6T@TTfhhG7pDYvg>ZLFO@f@v z zvUh`QR~}E*+O{)Q(r8GWNEsrMp`9e`%8;QHMZ7i@igrn;oygfrg9g)E z<_x>2PKKgRrsPB+^AtIu5QY?2~pk-Osi=Y@#rioLFm+7!ji-{q&NR_EDn9i?%~jHco?kEIts-ll`Q-?8UF~R{WUw>g z?;3eMzn}BWMl}1OzorU=vG`|Qz`9HE3pJS{L2=qQbWOP-L<(AjH@GQ%dxgDnJ3sVpM z(}w&Nb*V*b2)n$vwIc;!MK8sm_s4%euN=}!F=Rc^PiEc#WS8W6?vac3+?4i&Ee!ux z0GX`M-g#R1;8y0El^rce6FvOH)~uY~3M-@ZwXuiUv2RAe1NEmXYFt|b(jAKC-nL9> zqG$rUQ1rq8C3jOOcX)b19yx91=42Z=%|01XQT!B_RP{(wBB|dP?nk+=)0`c1<&R^l zj2;)wy`(ADjZ9cNY=KH3L`Sem3>L>iY zH#ZLiLBpI&rJ z@gKY(2fpoZtB7Iph)?YOHGaVGO3;|P{AGQ9=5{6I*_eETu%*1_>hwi6qI!P=ftQt+ z?~dXM&>k&TW<~&=%td!*lG!7Jpp^6rx%(M);YaHtT09;w9rKx7YR7sT6`!Rggq}5N zN@hA@EUsK|by7pm$uCJ-s&Mgo0(H8W23M1L#G&$|eRXxU8nRL)gZJt52Iaw%yLLq= z7{}Rj5mb}1^&?;VSD_!X;&R@sSETykH)!Mpv?_`4aH27vl<%Tn6t#wc1_pm4PbgS7 z#>U1MG*e4jlA9I3wBJuXS{OE60C%H7bFPY~7^pp*E3q!()BvG7xOkmsv9NQ)$TFM# zdOcCy4DrIo=oc@(L6EV`>I<{*kH-l*&~40Yi$!J30_eI!UItEWs$jEM0>!fT5YWt6 z>AWGYY=^=>-sjw~n>I~^YNWSehX<>@epqD1j_wQIy=QvoEzwbeR(8olW}!_SgsdV} z2;q-O3zSJi#QZ_AY?RCui0$TQ%578HUKXiN42^t_*&jV3OT4Kpi39PfE;TY)@aD48 z(gnS#i*$W+y~6rpsw9M)QbmHh`w2BEQ|MIM-T|$iPSJi|`ksg~)SyB!Sc?v78T4wY z$9eNb7UY8t#lPr--whUtj-hIhm}L_{1&I+C0`jK32K7E3yBqP8@lg zyr+;TNF}RzkkH!1{T?JLzE{x5c%jeoI0VOX_rkB-TUQ_To6X_Ek?T2QZ|oVCuRLqF zbcXiu$q=%QP_E-injY{c&^luj5A`pydiv%c%64#qCP}RL(sg0Q#hWc=+C}%hsA^u| z;^NX73l(Uuz6Ud8(8a8JradJ|Yg=+xQwU+ZxvQfj#MijgdV5|%-RsM%bc9eYnBzA5 zSTZN^fj#&vJy~`|U(iS4{X|%zA6d5)%BvVcWk_W$y>fQ@h^zA<5i&=dKNEJ?@9y)m z1stB_F29VNTvXH|5bFj;6Liw3@Q*zHkiluJBf);kn*SUC4 zeM|BBg>w|{cjp{cvh373xjyvzBzLS}_2Y zHQvP7*6}t3XJ$k?XH3<8lc4rSV}@V%f~^`&_s4l=%q>Ql1}LYmYR=~SW_JvXj{P_w zF>mD;-?34zjM{cI?GEC!DrY{{JR+jT5mH&xt$ zGq(REA5Woxn$h zU@9hJ8y+N(Q8XE^Lx7w_lrq16mZ%z0!YF`H#O|W6sK?1lS1^G}4bQG<=Q!+K2rqA& z>w}`;a3M(pk&{`$GN}?Fd%xynI$W7aHDeFX9^aa|{~+kX{y0t>ZVj4pE{JPFs$lja z73$=HR4|iHN1JAME`~*Uy$^~WG^94@Xlhyy(Ym3jKeqnL&8a(_#yuBM-;v^tDziDh zQR*?@)|jYPyV@cmGi3yrqtq5e;r4r z{i!u(IVW%!xZwEPt5%L6J_rg4Ndx~56cgq7_=uT03`?iq>Id|Fc+77-NqumOz?&;r z=>LXX<-{8qR=CUPx8oNOP!Fox`@!aJbf+&Dtp^p^=z#ZHP+*k(Y8E9%UO5s@`?xH} z1NWOIdOk2P41xI+xTTX%;dU6weu095L)W(5N*WncX3Fm%@xLj*{_jTQZ#(W-mdg`; z#O6q$r->fA~3|B@dQ9ZZmp-$3T92GN0d zTcvn>ges6L(F7l)BvR-XI&P}yBFcd)P;uY!q@chFysp%^!%|zf zH5`KzXVJBRSyE+oSFrDZX$Z}jaPlopMsoSm+c2u?B_Q9zXU~jV^G=2w31OzXKo3j5 z;W)Oha+H__NkwusWimUSy~^YSV%!q*Q4mQ4SJWJ^)}Y}~Rn<1D3iT?vt5H!0F5I)v z_B8Um47R6;GjAEG>r;#h%@dPfNKI~MsNf(tp^z#lA|h*KWYnEWr8cNx=X)O7Um5%1 zl$a_Xd!I}7<4k&K+?c`sVDfD5h4E4UWXlrlZvX=F`^UdLK0=z44LNRL#08d#A z@B5g>uR_X~jtWJT=KCf>VzqwI$({Wm@-G8P zeRXT`S8_*B{DwK*VD{XebaVru)+-mRf$oDAS-$3=v{bQScHRU%QpUm;u zu_2^&7zT|cwDR?OlFK}?n+L4?y1ShtDfF-_tL*JisgO=cNElluVil-Gq3lGEFN1QV zAngvy9GUL1rnA@%e*hEF10F)chW)|Bx6Pd9ck7nC(blbD5PK;>czOfUD_kn{F0+qt z#)4vE3dpDhY1ePukm;HiwSZh3AJ%4`FOc6(KE;U_`MY4I+nZDCq1uv;p3B>`oIf#s zcO$@tw`LY@&AVy=-#}+46vZ8c9Kn^kB6Rb%;0m# zWWS`z-73Tj?FuP zhK7&r9)*loL+tXkG69f$Y*S;4*-hF%smHvm>(`yIS2~Z}yLS({RO=`JQfI937a4p5ynxg=4Iwf>_xab#6u0Sa00cGA`Wc*Y@(`%b!2ru=M4VzI z5oQv!prCiAr<1S&jqv6l)oMN>eK!|MvVyNyO{?WBb@|9v-LYB~aPgvgGhOu~2#IDJ zH*Grkd9BswXqf%?nIJ<%d%c?*N_3y>^l(&T?-c3=f~7PxYru#7L??IgQ*KmEHF69C zCAC+anI#emYVG#?AM1Kj&NR-LyI3Af0pg)%@Z3bGsO)PJLB{XfRPaLh+AIFq*u)q6 zF&C@_*fZ4AU`7zxI~PkzDoYz&3dSR4suq{%S*6&>BDzAS(Vr2eIL9jxnx;YuKyCR$ zk2&n>3zMphfXJQy*<1GNr=kE2xw|+QUI2@o0O=;3#)*0U`mfW8Fy|UC5={Dqc|iqBxFx# zBeWendM)GYmshWowh7l4NTVx)9fGbH)3dY1+L|8h-%EZWAq^aJU;NT9{T%cCaHo+p zA@$*?Jn{9Jw!6`Kvd`*FpZvYRmTa^OB%=CRbK7G1V{KoPpz0_Ps>-4@ z%xY?C%2vGJZj~<$4Zt45V#fL#~ zl$Dizsvh$n*DQU47;x+_go6t^RJ_=fXmAQ!3l>-m&K5HbMu}LI@L6(Y!m&0aGc`+$i)pWu1Qh%hx{GJzZ%Fq)Gq4-o-y%ZWT=Oaj`3X|MDyL z69xr?SEj;V+JN)GavB9YxY&Iak+(HLv~s@*)ge;atQ|KOVORNLL!~$r%cB{&@u&kB*Fqt75LoWJJJT#7{pC zq~m3TZ+J%<#eezB7!XL?Mk#y!f4V;Bm1au4cEZb8b-G64|5)={d#rhwUO4lYt@zV& fsQ(W)(5SA{ri2Hl3W74G;h&NIR=w1XyHEWOL#wp_ literal 0 HcmV?d00001 From edac01ba3d3320a096be78497434cc03707aace8 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 7 Sep 2022 18:01:25 +0700 Subject: [PATCH 085/190] Fix hardfork issue --- contracts/ronin-validator/RoninValidatorSet.sol | 14 +++++++------- hardhat.config.ts | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index 12c223689..04f69f9bf 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -145,11 +145,10 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { uint256 _miningAmount = _miningReward[_validatorAddr]; _miningReward[_validatorAddr] = 0; if (_miningAmount > 0) { - // TODO(Thor): use `call` to transfer reward with reentrancy gruard - require( - payable(_staking.treasuryAddressOf(_validatorAddr)).send(_miningAmount), - "RoninValidatorSet: could not transfer RON" - ); + // TODO(Thor): consider using reentrancy gruard + address _treasury = _staking.treasuryAddressOf(_validatorAddr); + (bool _success, ) = _treasury.call{ value: _miningAmount }(""); + require(_success, "RoninValidatorSet: could not transfer RON validator addr"); } } @@ -164,8 +163,9 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _staking.settleRewardPools(_validators); if (_delegatingAmount > 0) { - // TODO(Thor): use `call` to transfer reward with reentrancy gruard - require(payable(address(_staking)).send(_delegatingAmount), "RoninValidatorSet: could not transfer RON"); + // TODO(Thor): consider using reentrancy gruard + (bool _success, ) = address(_staking).call{ value: _delegatingAmount }(""); + require(_success, "RoninValidatorSet: could not transfer RON to staking contract"); } _updateValidatorSet(); diff --git a/hardhat.config.ts b/hardhat.config.ts index 720b80eb8..125af326e 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -70,6 +70,7 @@ const config: HardhatUserConfig = { }, networks: { hardhat: { + hardfork: 'istanbul', accounts: { mnemonic: DEFAULT_MNEMONIC, count: 100, From 9584a452c60306b563dca60dcb585bc6b2ccc96b Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 7 Sep 2022 18:12:26 +0700 Subject: [PATCH 086/190] [README.md] Add ToC & update deployment --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/README.md b/README.md index 857f00cd0..e06121149 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,19 @@ The next version of smart contracts that power Ronin DPoS network. _NOTE: This smart contract version does not includes method to prevent the 51% attack. It is not finalized at the implementation time._ +- [Overview](#ronin-dpos-contracts) + - [Staking contract](#staking-contract) + - [Validator Candidate](#validator-candidate) + - [Delegator](#delegator) + - [Reward Calculation](#reward-calculation) + - [Validator Contract](#validator-contract) + - [Slashing](#slashing) + - [Contract Interaction flow](#contract-interaction-flow) +- [Development](#development) + - [Requirement](#requirement) + - [Compile & test](#compile---test) +- [Deployment](#deployment) + ## Staking contract An user can propose themselves to be a validator candidate by staking their RON. Other users are allowed to register as delegators by staking any amount of RON to the staking contract, he/she can choose a validator to stake their coins. @@ -84,3 +97,33 @@ The validators will be slashed when they do not provide the good service for Ron ## Contract Interaction flow Read the contract interaction flow at [DPoS Contract: Interaction Flow](https://www.notion.so/skymavis/DPoS-Contract-Interaction-Flow-3a535cf9048f46f69dd9a45958ad9b85). + +## Development + +### Requirement + +- Node@>=14 + Solc@^0.8.0 + Docker + +### Compile & test + +```shell +$ yarn install +$ yarn compile +$ yarn test +``` + +## Deployment + +- Init the environment variables: + +```shell +$ cp .env.example .env && vim .env +``` + +- Update the contract configuration in [`config.ts`](./src/config.ts#L54-L93) file + +- Deploy the contracts: + +```shell +$ yarn hardhat deploy --network +``` From aa8e330170afe4df3d59916ceaa92ad71cf4c5bd Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 7 Sep 2022 18:16:37 +0700 Subject: [PATCH 087/190] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e06121149..1c0e858a9 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ The ones on top `N` users with the highest amount of staked coins will become va | `uint256 commissionRate` | The rate to share for the validator. Values in range [0; 100_00] stands for [0; 100%] | | `address consensusAddr` | Address to produce block | | `address treasuryAddr` | Address to receive block reward | -| `msg.value` | The amount of RON to stake, require to larger than the minimum RON threshold to be validator | +| `msg.value` | The amount of RON to stake, require to be larger than the minimum RON threshold to be validator | The validator candidates can deposit or withdraw their funds afterwards as long as the staking balance must be greater than the minimum RON threshold. @@ -80,18 +80,18 @@ The validators will be slashed when they do not provide the good service for Ron **Unavailability** -- If a validator missed >= `misdemeanorThreshold` blocks in a day: Cannot claim the reward on that day +- If a validator missed >= `misdemeanorThreshold` blocks in a day: Cannot claim the reward on that day. - If a validator missed >= `felonyThreshold` blocks in a day: - Cannot claim the reward on that day. - Be slashed `slashFelonyAmount` amount of self-delegated RON. - - Be put in jail for `felonyThreshold` days. + - Be put in jail for `57600` blocks. **Double Sign** - If a validator submit more than 1 block at the same `block.number`: - Cannot claim the reward. - - Be put in jail for `type(uint256).max` days. + - Be put in jail for `type(uint256).max` blocks. - Be slashed `slashDoubleSignAmount` amount of self-delegated RON. ## Contract Interaction flow From fa2201549471fa0cac8e80623deb339dda360301 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Thu, 8 Sep 2022 10:34:51 +0700 Subject: [PATCH 088/190] Update requirements --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c0e858a9..7f1ed2927 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ Read the contract interaction flow at [DPoS Contract: Interaction Flow](https:// ### Requirement -- Node@>=14 + Solc@^0.8.0 + Docker +- Node@>=14 + Solc@^0.8.0 ### Compile & test From ade343865a61bb5964017c0e50c77e21cf59b910 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Thu, 8 Sep 2022 15:24:46 +0700 Subject: [PATCH 089/190] Remove unnecessary contracts & refine Validator contract --- contracts/interfaces/IRoninValidatorSet.sol | 46 +- contracts/interfaces/IValidatorSet.sol | 110 ---- contracts/mocks/MockEmptyValidatorSet.sol | 25 - ...l => MockRoninValidatorSetEpochSetter.sol} | 12 +- .../{validator => }/MockSlashIndicator.sol | 18 +- contracts/mocks/{staking => }/MockStaking.sol | 2 +- contracts/mocks/MockSwapStorage.sol | 3 +- contracts/mocks/MockValidatorSet.sol | 93 +++ contracts/mocks/MockValidatorSetCore.sol | 65 --- .../staking/MockValidatorSetForStaking.sol | 71 --- .../ronin-validator/RoninValidatorSet.sol | 104 ++-- .../{slash => slashing}/SlashIndicator.sol | 33 +- contracts/staking/DPoStaking.sol | 4 +- contracts/staking/Staking.sol | 523 ----------------- contracts/staking/StakingManager.sol | 4 +- contracts/validator/ValidatorSet.sol | 193 ------- contracts/validator/ValidatorSetCore.sol | 156 ------ src/config.ts | 11 +- src/deploy/proxy/ronin-validator-proxy.ts | 2 - src/deploy/proxy/slash-indicator-proxy.ts | 3 + test/slash/SlashIndicator.test.ts | 22 +- test/staking/DPoStaking.test.ts | 13 +- test/staking/Staking.test.ts | 528 ------------------ test/validator/RoninValidatorSet.test.ts | 18 +- test/validator/ValidatorSetCore.test.ts | 199 ------- 25 files changed, 253 insertions(+), 2005 deletions(-) delete mode 100644 contracts/interfaces/IValidatorSet.sol delete mode 100644 contracts/mocks/MockEmptyValidatorSet.sol rename contracts/mocks/{validator/MockValidator.sol => MockRoninValidatorSetEpochSetter.sol} (80%) rename contracts/mocks/{validator => }/MockSlashIndicator.sol (68%) rename contracts/mocks/{staking => }/MockStaking.sol (98%) create mode 100644 contracts/mocks/MockValidatorSet.sol delete mode 100644 contracts/mocks/MockValidatorSetCore.sol delete mode 100644 contracts/mocks/staking/MockValidatorSetForStaking.sol rename contracts/{slash => slashing}/SlashIndicator.sol (80%) delete mode 100644 contracts/staking/Staking.sol delete mode 100644 contracts/validator/ValidatorSet.sol delete mode 100644 contracts/validator/ValidatorSetCore.sol delete mode 100644 test/staking/Staking.test.ts delete mode 100644 test/validator/ValidatorSetCore.test.ts diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index de1092541..2e83891b8 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -5,6 +5,13 @@ pragma solidity ^0.8.9; import "./ISlashIndicator.sol"; interface IRoninValidatorSet { + /// @dev Emitted when the reward of the valdiator is deprecated + event RewardDeprecated(address coinbaseAddr, uint256 rewardAmount); + /// @dev Emitted when the block reward is submitted + event BlockRewardSubmitted(address coinbaseAddr, uint256 rewardAmount); + /// @dev Emitted when the validator is slashed. + event ValidatorSlashed(address validatorAddr, uint256 jailedUntil, uint256 deductedStakingAmount); + /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR COINBASE // /////////////////////////////////////////////////////////////////////////////////////// @@ -51,31 +58,18 @@ interface IRoninValidatorSet { */ function stakingContract() external view returns (address); - /* @dev Slashes the validator that missed 50 block a day - * - * Requirements: - * - The method caller is slash indicator contract. - * - */ - function slashMisdemeanor(address _validatorAddr) external; - - /** - * @dev Slashes the validator that missed 150 block a day - * - * Requirements: - * - The method caller is slash indicator contract. - * - */ - function slashFelony(address _validatorAddr) external; - /** - * @dev Slashes the validator that created 2 blocks on a same height + * @dev Slashes the validator. * * Requirements: * - The method caller is slash indicator contract. * */ - function slashDoubleSign(address _validatorAddr) external; + function slash( + address _validatorAddr, + uint256 _newJailedUntil, + uint256 _slashAmount + ) external; /** * @dev Returns whether the validators are put in jail (cannot join the set of validators) during the current period. @@ -83,19 +77,9 @@ interface IRoninValidatorSet { function jailed(address[] memory) external view returns (bool[] memory); /** - * @dev Returns whether the incoming reward of the validators are sinked during the period. - */ - function noPendingReward(address[] memory, uint256 _period) external view returns (bool[] memory); - - /** - * @dev The amount of RON to slash felony. - */ - function slashFelonyAmount() external view returns (uint256); - - /** - * @dev The amount of RON to slash felony. + * @dev Returns whether the incoming reward of the validators are deprecated during the period. */ - function slashDoubleSignAmount() external view returns (uint256); + function rewardDeprecated(address[] memory, uint256 _period) external view returns (bool[] memory); /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR NORMAL USER // diff --git a/contracts/interfaces/IValidatorSet.sol b/contracts/interfaces/IValidatorSet.sol deleted file mode 100644 index 7abf84dd9..000000000 --- a/contracts/interfaces/IValidatorSet.sol +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -/** - * @title Set of validators in current epoch - * @notice This contract maintains set of validator in the current epoch of Ronin network - */ -interface IValidatorSet { - struct Validator { - /// @dev Address of the validator that produces block, e.g. block.coinbase - address consensusAddr; - /// @dev Address of treasury of the validator, used for double-checking validator information - address treasuryAddr; - /// @dev State if the validator is in jail - bool jailed; - /// @dev For upgrability purpose - /// @custom:note Consider leave or keep this attribute - uint256[20] ___gap; - } - - /** - * @notice The block producer update the validator set at the end of each epoch. - * - * @dev This method is called at the end of each epoch by comparing on `block.number`. The next - * validator set is not sent by the validator, but is fetched and calculated from `Stake.sol` - * contract. The order in the set of the validator is also the order to mining block in the next - * epoch. Voting power (VP) of each validator only affects their shared mining reward. - * - * @custom:note Consider only allowing coinbase to call this method - */ - function updateValidators() external returns (address[] memory); - - /** - * @notice The block producers call this method to send the mining reward in coinbase transaction. - * - * @dev Requirements: - * - Only coinbase address can call this method - **/ - function depositReward() external payable; - - /** - * @notice Slash the validator that missed 50 block a day - * - * @dev Requirements: - * - Only slash contract can call this method - */ - function slashMisdemeanor(address validator) external; - - /** - * @notice Slash the validator that missed 150 block a day - * - * @dev Requirements: - * - Only slash contract can call this method - */ - function slashFelony(address validator) external; - - /** - * @notice Slash the validator that created 2 blocks on a same height - * - * @dev Requirements: - * - Only slash contract can call this method - */ - function slashDoubleSign(address validator) external; - - /// - /// QUERY FUNCTIONS - /// - - /** - * @notice All validators call this method to get the set of validators in current epoch. - * Primarily, this method will be called at the beginning of each epoch. - */ - function getValidators() external view returns (address[] memory); - - /** - * @notice Check if an address is a validator - */ - function isValidator(address validator) external view returns (bool); - - /** - * @notice Check if an address is a validator not being in jail - */ - function isWorkingValidator(address validator) external view returns (bool); - - /** - * @notice Check if an address is in the validator list of the current epoch - */ - function isCurrentValidator(address validator) external view returns (bool); - - /** - * @notice Return last block height when the set of validators is updated - */ - function getLastUpdated() external view returns (uint256 height); - - /** - * @dev Returns the number of epochs in a period. - */ - function numberOfEpochsInPeriod() external view returns (uint256 _numberOfEpochs); - - /** - * @dev Returns the number of blocks in a epoch. - */ - function numberOfBlocksInEpoch() external view returns (uint256 _numberOfBlocks); - - /** - * @dev Returns the period index from the block number. - */ - function periodOf(uint256 _block) external view returns (uint256); -} diff --git a/contracts/mocks/MockEmptyValidatorSet.sol b/contracts/mocks/MockEmptyValidatorSet.sol deleted file mode 100644 index e2a9edc01..000000000 --- a/contracts/mocks/MockEmptyValidatorSet.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -import "../interfaces/ISlashIndicator.sol"; - -contract MockEmptyValidatorSet { - ISlashIndicator private __slashingContract; - - function _setSlashingContract() internal view virtual returns (ISlashIndicator) { - return __slashingContract; - } - - function setSlashingContract(ISlashIndicator _addr) external { - __slashingContract = _addr; - } - - function slashFelony(address _addr) external {} - - function slashMisdemeanor(address _addr) external {} - - function resetCounters(address[] calldata _addr) external { - __slashingContract.resetCounters(_addr); - } -} diff --git a/contracts/mocks/validator/MockValidator.sol b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol similarity index 80% rename from contracts/mocks/validator/MockValidator.sol rename to contracts/mocks/MockRoninValidatorSetEpochSetter.sol index 4734cdf7c..f757282d3 100644 --- a/contracts/mocks/validator/MockValidator.sol +++ b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.9; -import "../../ronin-validator/RoninValidatorSet.sol"; +import "../ronin-validator/RoninValidatorSet.sol"; -contract MockRoninValidatorSet is RoninValidatorSet { +contract MockRoninValidatorSetEpochSetter is RoninValidatorSet { uint256[] internal _epochs; uint256[] internal _periods; @@ -12,16 +12,12 @@ contract MockRoninValidatorSet is RoninValidatorSet { address __governanceAdminContract, address __slashIndicatorContract, address __stakingContract, - uint256 __maxValidatorNumber, - uint256 _slashFelonyAmount, - uint256 _slashDoubleSignAmount + uint256 __maxValidatorNumber ) { _governanceAdminContract = __governanceAdminContract; _slashIndicatorContract = __slashIndicatorContract; _stakingContract = __stakingContract; _maxValidatorNumber = __maxValidatorNumber; - slashFelonyAmount = _slashFelonyAmount; - slashDoubleSignAmount = _slashDoubleSignAmount; } function endEpoch() external { @@ -41,7 +37,7 @@ contract MockRoninValidatorSet is RoninValidatorSet { } function epochOf(uint256 _block) public view override returns (uint256 _epoch) { - for (uint256 _i; _i < _periods.length; _i++) { + for (uint256 _i; _i < _epochs.length; _i++) { if (_block >= _epochs[_i]) { _epoch = _i + 1; } diff --git a/contracts/mocks/validator/MockSlashIndicator.sol b/contracts/mocks/MockSlashIndicator.sol similarity index 68% rename from contracts/mocks/validator/MockSlashIndicator.sol rename to contracts/mocks/MockSlashIndicator.sol index 7b91087f5..c9e5fb290 100644 --- a/contracts/mocks/validator/MockSlashIndicator.sol +++ b/contracts/mocks/MockSlashIndicator.sol @@ -2,27 +2,35 @@ pragma solidity ^0.8.9; -import "../../interfaces/IRoninValidatorSet.sol"; -import "../../interfaces/ISlashIndicator.sol"; +import "../interfaces/IRoninValidatorSet.sol"; +import "../interfaces/ISlashIndicator.sol"; contract MockSlashIndicator is ISlashIndicator { IRoninValidatorSet public validatorContract; + uint256 public slashFelonyAmount; + uint256 public slashDoubleSignAmount; modifier onlyCoinbase() { require(msg.sender == block.coinbase, "SlashIndicator: method caller is not the coinbase"); _; } - constructor(IRoninValidatorSet _validatorSetContract) { + constructor( + IRoninValidatorSet _validatorSetContract, + uint256 _slashFelonyAmount, + uint256 _slashDoubleSignAmount + ) { validatorContract = _validatorSetContract; + slashFelonyAmount = _slashFelonyAmount; + slashDoubleSignAmount = _slashDoubleSignAmount; } function slashFelony(address _validatorAddr) external { - validatorContract.slashFelony(_validatorAddr); + validatorContract.slash(_validatorAddr, 0, 0); } function slashMisdemeanor(address _validatorAddr) external { - validatorContract.slashMisdemeanor(_validatorAddr); + validatorContract.slash(_validatorAddr, 0, 0); } function resetCounters(address[] calldata) external {} diff --git a/contracts/mocks/staking/MockStaking.sol b/contracts/mocks/MockStaking.sol similarity index 98% rename from contracts/mocks/staking/MockStaking.sol rename to contracts/mocks/MockStaking.sol index 0d79cc82c..9234ce885 100644 --- a/contracts/mocks/staking/MockStaking.sol +++ b/contracts/mocks/MockStaking.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.9; -import "../../staking/RewardCalculation.sol"; +import "../staking/RewardCalculation.sol"; contract MockStaking is RewardCalculation { /// @dev Mapping from user => staking balance diff --git a/contracts/mocks/MockSwapStorage.sol b/contracts/mocks/MockSwapStorage.sol index 51dd672db..7ef601831 100644 --- a/contracts/mocks/MockSwapStorage.sol +++ b/contracts/mocks/MockSwapStorage.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.9; -import "../interfaces/IValidatorSet.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import "../interfaces/IStaking.sol"; contract MockSwapStorage { diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol new file mode 100644 index 000000000..2c040fedf --- /dev/null +++ b/contracts/mocks/MockValidatorSet.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../interfaces/ISlashIndicator.sol"; +import "../interfaces/IRoninValidatorSet.sol"; +import "../interfaces/IStaking.sol"; + +contract MockValidatorSet is IRoninValidatorSet { + address public stakingContract; + address public slashIndicatorContract; + + uint256 public numberOfEpochsInPeriod; + uint256 public numberOfBlocksInEpoch; + /// @dev Mapping from period number => slashed + mapping(uint256 => bool) internal _periodSlashed; + uint256[] internal _periods; + + constructor( + address _stakingContract, + address _slashIndicatorContract, + uint256 _numberOfEpochsInPeriod, + uint256 _numberOfBlocksInEpoch + ) { + stakingContract = _stakingContract; + slashIndicatorContract = _slashIndicatorContract; + numberOfEpochsInPeriod = _numberOfEpochsInPeriod; + numberOfBlocksInEpoch = _numberOfBlocksInEpoch; + } + + function depositReward() external payable { + IStaking(stakingContract).recordReward{ value: msg.value }(msg.sender, msg.value); + } + + function settledReward(address[] calldata _validatorList) external { + IStaking(stakingContract).settleRewardPools(_validatorList); + } + + function slashMisdemeanor(address _validator) external { + IStaking(stakingContract).sinkPendingReward(_validator); + } + + function slashFelony(address _validator) external { + IStaking(stakingContract).sinkPendingReward(_validator); + IStaking(stakingContract).deductStakingAmount(_validator, 1); + } + + function slashDoubleSign(address _validator) external { + IStaking(stakingContract).sinkPendingReward(_validator); + } + + function endPeriod() external { + _periods.push(block.number); + } + + function periodOf(uint256 _block) external view override returns (uint256 _period) { + for (uint256 _i; _i < _periods.length; _i++) { + if (_block >= _periods[_i]) { + _period = _i + 1; + } + } + } + + function submitBlockReward() external payable override {} + + function wrapUpEpoch() external payable override {} + + function getLastUpdatedBlock() external view override returns (uint256) {} + + function governanceAdminContract() external view override returns (address) {} + + function jailed(address[] memory) external view override returns (bool[] memory) {} + + function rewardDeprecated(address[] memory, uint256 _period) external view override returns (bool[] memory) {} + + function epochOf(uint256 _block) external view override returns (uint256) {} + + function getValidators() external view override returns (address[] memory) {} + + function epochEndingAt(uint256 _block) external view override returns (bool) {} + + function periodEndingAt(uint256 _block) external view override returns (bool) {} + + function slash( + address _validatorAddr, + uint256 _newJailedUntil, + uint256 _slashAmount + ) external override {} + + function resetCounters(address[] calldata _validatorAddrs) external { + ISlashIndicator(slashIndicatorContract).resetCounters(_validatorAddrs); + } +} diff --git a/contracts/mocks/MockValidatorSetCore.sol b/contracts/mocks/MockValidatorSetCore.sol deleted file mode 100644 index 7087c5e28..000000000 --- a/contracts/mocks/MockValidatorSetCore.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -import "../validator/ValidatorSetCore.sol"; -import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; - -/** - * @title Set of validators in current epoch - * @notice This contract maintains set of validator in the current epoch of Ronin network - */ -contract MockValidatorSetCore is ValidatorSetCore { - function isInCurrentValidatorSet(address _addr) external view returns (bool) { - return _isInCurrentValidatorSet(_addr); - } - - function getCurrentValidatorSetSize() external view returns (uint256) { - return _getCurrentValidatorSetSize(); - } - - function setValidatorAtMiningIndex(uint256 _miningIndex, IStaking.ValidatorCandidate memory _incomingValidator) - external - { - _setValidatorAtMiningIndex(_miningIndex, _incomingValidator); - } - - function setValidator(IStaking.ValidatorCandidate memory _incomingValidator, bool _forced) - external - returns (uint256) - { - return _setValidator(_incomingValidator, _forced); - } - - function popValidatorFromMiningIndex() external { - _popValidatorFromMiningIndex(); - } - - function getValidatorAtMiningIndex(uint256 _miningIndex) external view returns (IValidatorSet.Validator memory) { - return _getValidatorAtMiningIndex(_miningIndex); - } - - function getValidator(address _valAddr) external view returns (IValidatorSet.Validator memory) { - return _getValidator(_valAddr); - } - - function tryGetValidator(address _valAddr) - external - view - returns ( - bool, - IValidatorSet.Validator memory, - uint256 - ) - { - return _tryGetValidator(_valAddr); - } - - function isSameValidator(IStaking.ValidatorCandidate memory _v1, IValidatorSet.Validator memory _v2) - external - pure - returns (bool) - { - return _isSameValidator(_v1, _v2); - } -} diff --git a/contracts/mocks/staking/MockValidatorSetForStaking.sol b/contracts/mocks/staking/MockValidatorSetForStaking.sol deleted file mode 100644 index 0f06a2608..000000000 --- a/contracts/mocks/staking/MockValidatorSetForStaking.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -import "../../interfaces/IValidatorSet.sol"; -import "../../interfaces/IStaking.sol"; - -contract MockValidatorSetForStaking is IValidatorSet { - IStaking public stakingContract; - - uint256 public numberOfEpochsInPeriod; - uint256 public numberOfBlocksInEpoch; - /// @dev Mapping from period number => slashed - mapping(uint256 => bool) internal _periodSlashed; - uint256[] internal _periods; - - constructor( - IStaking _stakingContract, - uint256 _numberOfEpochsInPeriod, - uint256 _numberOfBlocksInEpoch - ) { - stakingContract = _stakingContract; - numberOfEpochsInPeriod = _numberOfEpochsInPeriod; - numberOfBlocksInEpoch = _numberOfBlocksInEpoch; - } - - function depositReward() external payable override { - stakingContract.recordReward{ value: msg.value }(msg.sender, msg.value); - } - - function settledReward(address[] calldata _validatorList) external { - stakingContract.settleRewardPools(_validatorList); - } - - function slashMisdemeanor(address _validator) external override { - stakingContract.sinkPendingReward(_validator); - } - - function slashFelony(address _validator) external override { - stakingContract.sinkPendingReward(_validator); - stakingContract.deductStakingAmount(_validator, 1); - } - - function slashDoubleSign(address _validator) external override { - stakingContract.sinkPendingReward(_validator); - } - - function endPeriod() external { - _periods.push(block.number); - } - - function periodOf(uint256 _block) external view override returns (uint256 _period) { - for (uint256 _i; _i < _periods.length; _i++) { - if (_block >= _periods[_i]) { - _period = _i + 1; - } - } - } - - function updateValidators() external override returns (address[] memory) {} - - function getValidators() external view override returns (address[] memory) {} - - function isValidator(address validator) external view override returns (bool) {} - - function isWorkingValidator(address validator) external view override returns (bool) {} - - function isCurrentValidator(address validator) external view override returns (bool) {} - - function getLastUpdated() external view override returns (uint256 height) {} -} diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index 04f69f9bf..ccabda9c5 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -33,9 +33,11 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { uint256 internal _numberOfEpochsInPeriod; /// @dev The last updated block uint256 internal _lastUpdatedBlock; + /// @dev Mapping from epoch index => flag indicating the epoch is wrapped up or not + mapping(uint256 => bool) internal _wrappedUp; - /// @dev Mapping from period index => validator address => flag indicating whether the validator has no pending reward in that period - mapping(uint256 => mapping(address => bool)) internal _noPendingReward; + /// @dev Mapping from validator address => the last **period** that the validator has no pending reward + mapping(address => mapping(uint256 => bool)) internal _rewardDeprecatedAtPeriod; /// @dev Mapping from validator address => the last block that the validator is jailed mapping(address => uint256) internal _jailedUntil; @@ -44,11 +46,6 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { /// @dev Mapping from validator address => pending reward from delegating mapping(address => uint256) internal _delegatingReward; - /// @dev The amount of RON to slash felony. - uint256 public slashFelonyAmount; - /// @dev The amount of RON to slash double sign. - uint256 public slashDoubleSignAmount; - modifier onlyCoinbase() { require(msg.sender == block.coinbase, "RoninValidatorSet: method caller must be coinbase"); _; @@ -77,9 +74,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { address __stakingContract, uint256 __maxValidatorNumber, uint256 __numberOfBlocksInEpoch, - uint256 __numberOfEpochsInPeriod, - uint256 _slashFelonyAmount, - uint256 _slashDoubleSignAmount + uint256 __numberOfEpochsInPeriod ) external initializer { _governanceAdminContract = __governanceAdminContract; _slashIndicatorContract = __slashIndicatorContract; @@ -87,8 +82,6 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _maxValidatorNumber = __maxValidatorNumber; _numberOfBlocksInEpoch = __numberOfBlocksInEpoch; _numberOfEpochsInPeriod = __numberOfEpochsInPeriod; - slashFelonyAmount = _slashFelonyAmount; - slashDoubleSignAmount = _slashDoubleSignAmount; } /////////////////////////////////////////////////////////////////////////////////////// @@ -100,34 +93,39 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { */ function submitBlockReward() external payable override onlyCoinbase { uint256 _reward = msg.value; - address _validatorAddr = msg.sender; - if (!_isValidator(_validatorAddr)) { - // TODO(Thor): emit the deprecated reward event + if (_reward == 0) { return; } - if (_jailed(_validatorAddr) || _noPendingReward[periodOf(block.number)][_validatorAddr]) { - // TODO(Thor): emit the deprecated reward event + address _coinbaseAddr = msg.sender; + // Deprecates reward for non-validator or slashed validator + if ( + !_isValidator(_coinbaseAddr) || _jailed(_coinbaseAddr) || _rewardDeprecated(_coinbaseAddr, periodOf(block.number)) + ) { + emit RewardDeprecated(_coinbaseAddr, _reward); return; } + emit BlockRewardSubmitted(_coinbaseAddr, _reward); IStaking _staking = IStaking(_stakingContract); - uint256 _rate = _staking.commissionRateOf(_validatorAddr); + uint256 _rate = _staking.commissionRateOf(_coinbaseAddr); uint256 _miningAmount = (_rate * _reward) / 100_00; uint256 _delegatingAmount = _reward - _miningAmount; - _miningReward[_validatorAddr] += _miningAmount; - _delegatingReward[_validatorAddr] += _delegatingAmount; - IStaking(_stakingContract).recordReward(_validatorAddr, _delegatingAmount); - // TODO(Thor): emit event + _miningReward[_coinbaseAddr] += _miningAmount; + _delegatingReward[_coinbaseAddr] += _delegatingAmount; + _staking.recordReward(_coinbaseAddr, _delegatingAmount); } /** * @inheritdoc IRoninValidatorSet */ function wrapUpEpoch() external payable override onlyCoinbase whenEpochEnding { - IStaking _staking = IStaking(_stakingContract); + uint256 _epoch = epochOf(block.number); + require(_wrappedUp[_epoch] == false, "RoninValidatorSet: query for already wrapped up epoch"); + _wrappedUp[_epoch] = true; + IStaking _staking = IStaking(_stakingContract); address _validatorAddr; uint256 _delegatingAmount; uint256 _period = periodOf(block.number); @@ -137,23 +135,22 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { for (uint _i = 0; _i < _validators.length; _i++) { _validatorAddr = _validators[_i]; - if (_jailed(_validatorAddr) || _noPendingReward[_period][_validatorAddr]) { + if (_jailed(_validatorAddr) || _rewardDeprecated(_validatorAddr, _period)) { continue; } if (_periodEnding) { uint256 _miningAmount = _miningReward[_validatorAddr]; - _miningReward[_validatorAddr] = 0; + delete _miningReward[_validatorAddr]; if (_miningAmount > 0) { - // TODO(Thor): consider using reentrancy gruard address _treasury = _staking.treasuryAddressOf(_validatorAddr); (bool _success, ) = _treasury.call{ value: _miningAmount }(""); - require(_success, "RoninValidatorSet: could not transfer RON validator addr"); + require(_success, "RoninValidatorSet: could not transfer RON treasury addr"); } } _delegatingAmount += _delegatingReward[_validatorAddr]; - _delegatingReward[_validatorAddr] = 0; + delete _delegatingReward[_validatorAddr]; // TODO: emit events } @@ -163,7 +160,6 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _staking.settleRewardPools(_validators); if (_delegatingAmount > 0) { - // TODO(Thor): consider using reentrancy gruard (bool _success, ) = address(_staking).call{ value: _delegatingAmount }(""); require(_success, "RoninValidatorSet: could not transfer RON to staking contract"); } @@ -206,32 +202,25 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { /** * @inheritdoc IRoninValidatorSet */ - function slashMisdemeanor(address _validatorAddr) public override onlySlashIndicatorContract { - _noPendingReward[periodOf(block.number)][_validatorAddr] = true; - _miningReward[_validatorAddr] = 0; - _delegatingReward[_validatorAddr] = 0; + function slash( + address _validatorAddr, + uint256 _newJailedUntil, + uint256 _slashAmount + ) external onlySlashIndicatorContract { + _rewardDeprecatedAtPeriod[_validatorAddr][periodOf(block.number)] = true; + delete _miningReward[_validatorAddr]; + delete _delegatingReward[_validatorAddr]; IStaking(_stakingContract).sinkPendingReward(_validatorAddr); - } - /** - * @inheritdoc IRoninValidatorSet - */ - function slashFelony(address _validatorAddr) external override onlySlashIndicatorContract { - slashMisdemeanor(_validatorAddr); - IStaking(_stakingContract).deductStakingAmount(_validatorAddr, slashFelonyAmount); - uint256 _jailedBlock = block.number + 2 * 28800; // TODO: make this constant number to variable - _jailedUntil[_validatorAddr] = Math.max(_jailedUntil[_validatorAddr], _jailedBlock); - // TODO(Thor): emit event - } + if (_newJailedUntil > 0) { + _jailedUntil[_validatorAddr] = Math.max(_newJailedUntil, _jailedUntil[_validatorAddr]); + } - /** - * @inheritdoc IRoninValidatorSet - */ - function slashDoubleSign(address _validatorAddr) external override onlySlashIndicatorContract { - slashMisdemeanor(_validatorAddr); - IStaking(_stakingContract).deductStakingAmount(_validatorAddr, slashDoubleSignAmount); - _jailedUntil[_validatorAddr] = type(uint256).max; - // TODO(Thor): emit event + if (_slashAmount > 0) { + IStaking(_stakingContract).deductStakingAmount(_validatorAddr, _slashAmount); + } + + emit ValidatorSlashed(_validatorAddr, _jailedUntil[_validatorAddr], _slashAmount); } /** @@ -246,14 +235,14 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { /** * @inheritdoc IRoninValidatorSet */ - function noPendingReward(address[] memory _addrList, uint256 _period) + function rewardDeprecated(address[] memory _addrList, uint256 _period) external view override returns (bool[] memory _result) { for (uint256 _i; _i < _addrList.length; _i++) { - _result[_i] = _noPendingReward[_period][_addrList[_i]]; + _result[_i] = _rewardDeprecated(_addrList[_i], _period); } } @@ -321,6 +310,13 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { return block.number <= _jailedUntil[_validatorAddr]; } + /** + * @dev Returns whether the validator has no pending reward in that period. + */ + function _rewardDeprecated(address _validatorAddr, uint256 _period) internal view returns (bool) { + return _rewardDeprecatedAtPeriod[_validatorAddr][_period]; + } + /** * @dev Returns whether the address `_addr` is validator or not. */ diff --git a/contracts/slash/SlashIndicator.sol b/contracts/slashing/SlashIndicator.sol similarity index 80% rename from contracts/slash/SlashIndicator.sol rename to contracts/slashing/SlashIndicator.sol index a7d1f1ee1..627a47040 100644 --- a/contracts/slash/SlashIndicator.sol +++ b/contracts/slashing/SlashIndicator.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "../interfaces/ISlashIndicator.sol"; -import "../interfaces/IStaking.sol"; import "../interfaces/IRoninValidatorSet.sol"; contract SlashIndicator is ISlashIndicator, Initializable { @@ -12,12 +11,22 @@ contract SlashIndicator is ISlashIndicator, Initializable { mapping(address => uint256) internal _unavailabilityIndicator; /// @dev The last block that a validator is slashed uint256 public lastSlashedBlock; + /// @dev The threshold to slash when validator is unavailability reaches misdemeanor uint256 public misdemeanorThreshold; /// @dev The threshold to slash when validator is unavailability reaches felony uint256 public felonyThreshold; + + /// @dev The amount of RON to slash felony. + uint256 public slashFelonyAmount; + /// @dev The amount of RON to slash double sign. + uint256 public slashDoubleSignAmount; + /// @dev The block duration to jail validator that reaches felony thresold. + uint256 public felonyJailDuration; + /// @dev The validator contract IRoninValidatorSet public validatorContract; + /// @dev The governance admin contract address internal _governanceAdminContract; modifier onlyCoinbase() { @@ -49,11 +58,17 @@ contract SlashIndicator is ISlashIndicator, Initializable { function initialize( uint256 _misdemeanorThreshold, uint256 _felonyThreshold, - IRoninValidatorSet _validatorSetContract + IRoninValidatorSet _validatorSetContract, + uint256 _slashFelonyAmount, + uint256 _slashDoubleSignAmount, + uint256 _felonyJailBlocks ) external initializer { misdemeanorThreshold = _misdemeanorThreshold; felonyThreshold = _felonyThreshold; validatorContract = _validatorSetContract; + slashFelonyAmount = _slashFelonyAmount; + slashDoubleSignAmount = _slashDoubleSignAmount; + felonyJailDuration = _felonyJailBlocks; } /////////////////////////////////////////////////////////////////////////////////////// @@ -72,19 +87,25 @@ contract SlashIndicator is ISlashIndicator, Initializable { // Slashs the validator as either the fenoly or the misdemeanor if (_count == felonyThreshold) { - validatorContract.slashFelony(_validatorAddr); emit ValidatorSlashed(_validatorAddr, SlashType.FELONY); + validatorContract.slash(_validatorAddr, block.number + felonyJailDuration, slashFelonyAmount); } else if (_count == misdemeanorThreshold) { - validatorContract.slashMisdemeanor(_validatorAddr); emit ValidatorSlashed(_validatorAddr, SlashType.MISDEMEANOR); + validatorContract.slash(_validatorAddr, 0, 0); } } /** * @inheritdoc ISlashIndicator */ - function slashDoubleSign(address _valAddr, bytes calldata _evidence) external override onlyCoinbase { - revert("Not implemented"); + function slashDoubleSign( + address _validatorAddr, + bytes calldata /* _evidence */ + ) external override onlyCoinbase { + bool _proved = false; // Proves the `_evidence` is right + if (_proved) { + validatorContract.slash(_validatorAddr, type(uint256).max, slashDoubleSignAmount); + } } /** diff --git a/contracts/staking/DPoStaking.sol b/contracts/staking/DPoStaking.sol index 995229dfd..feb5566b6 100644 --- a/contracts/staking/DPoStaking.sol +++ b/contracts/staking/DPoStaking.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.9; // import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "../interfaces/IStaking.sol"; -import "../interfaces/IValidatorSet.sol"; +import "../interfaces/IRoninValidatorSet.sol"; import "../libraries/Sorting.sol"; import "./StakingManager.sol"; @@ -271,7 +271,7 @@ contract DPoStaking is IStaking, StakingManager, Initializable { * @inheritdoc RewardCalculation */ function _periodOf(uint256 _block) internal view virtual override returns (uint256) { - return IValidatorSet(_validatorContract).periodOf(_block); + return IRoninValidatorSet(_validatorContract).periodOf(_block); } /** diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol deleted file mode 100644 index 1b3b78cc4..000000000 --- a/contracts/staking/Staking.sol +++ /dev/null @@ -1,523 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "../interfaces/IStaking.sol"; -import "../interfaces/IValidatorSet.sol"; -import "../libraries/Sorting.sol"; -import "hardhat/console.sol"; - -contract Staking is IStaking, Initializable { - /// TODO: expose fn to get validator info by validator address. - /// @dev Mapping from consensus address => validator index in `validatorCandidates`. - mapping(address => uint256) internal _validatorIndexes; - /// TODO: expose fn returns the whole validator array. - /// @dev Validator array. The order of the validator is assured not to be changed, since this - /// array is kept synced with the array in the `Staking` contract. The element at 0-index is - /// always an empty validator. - ValidatorCandidate[] public validatorCandidates; - - /// @dev Index of validators that are mining in the current epoch. Get updated each epoch. - /// Element at 0-index is always `0`. - uint256[] internal _currentValidatorIndexes; - - /// @dev Index of validators that are on renounce - uint256[] internal _pendingRenouncingValidatorIndexes; - - /// @dev Mapping from delegator address => consensus address => delegated amount. - mapping(address => mapping(address => uint256)) delegatedAmount; - - // TODO: expose this fn in the interface - /// @dev Configuration of maximum number of validator - uint256 public totalValidatorThreshold; - - // TODO: expose this fn in the interface - /// @dev Configuration of number of blocks that validator has to wait before unstaking, counted - /// from staking time - uint256 public unstakingOnHoldBlocksNum; - - // TODO: expose this fn in the interface - /// @dev Configuration of minimum balance for being a validator - uint256 public minValidatorBalance; - - /// @dev Number of maximum working validator in one epoch - uint256 public numOfCabinets; - - /// @dev Validator contract address - IValidatorSet public validatorSetContract; - - /// @dev Helper global flag which is set to true on at least one validator balance changes, and - /// is reset to false per epoch. This help reduces redundant sortings. - bool internal _globalBalanceChanged; - - modifier onlyValidatorSetContract() { - require(msg.sender == address(validatorSetContract), "Only validator set contract"); - _; - } - - constructor() { - _disableInitializers(); - } - - /** - * @dev Initializes the contract storage. - */ - function initialize() external initializer { - /// TODO: bring these constant variable to params. - numOfCabinets = 21; - totalValidatorThreshold = 50; - unstakingOnHoldBlocksNum = 28800; // 28800 blocks ~= 1 day - minValidatorBalance = 3 * 1e6 * 1e18; // 3m RON - /// Add empty validator at 0-index - validatorCandidates.push(); - } - - /////////////////////////////////////////////////////////////////////////////////////// - // FUNCTIONS FOR GOVERNANCE // - /////////////////////////////////////////////////////////////////////////////////////// - - // TODO: restrict to only govenance - function setValidatorSetContract(IValidatorSet _validatorSetContract) external { - validatorSetContract = _validatorSetContract; - } - - // TODO: restrict to only govenance - function setNumOfCabinets(uint256 _numOfCabinets) external { - numOfCabinets = _numOfCabinets; - } - - /////////////////////////////////////////////////////////////////////////////////////// - // FUNCTIONS FOR VALIDATORS // - /////////////////////////////////////////////////////////////////////////////////////// - - /** - * @dev See {IStaking-proposeValidator}. - */ - function proposeValidator( - address _consensusAddr, - address payable _treasuryAddr, - uint256 _commissionRate - ) external payable returns (uint256 _candidateIdx) { - uint256 _amount = msg.value; - address _stakingAddr = msg.sender; - - require(validatorCandidates.length < totalValidatorThreshold, "Staking: query for exceeded validator array length"); - require(_amount >= minValidatorBalance, "Staking: insuficient amount"); - require(_treasuryAddr != address(0), "Staking: invalid treasury address"); - - ValidatorCandidate storage _candidate; - if (_validatorIndexes[_consensusAddr] > 0) { - /// renounced validator joins as a validator again - (, _candidate) = _getDeprecatedCandidate(_consensusAddr); - require(_candidate.state == ValidatorState.RENOUNCED, "Staking: cannot propose an existed candidate"); - _candidate.state = ValidatorState.ACTIVE; - } else { - /// totally new validator joins - _candidateIdx = validatorCandidates.length; - _candidate = validatorCandidates.push(); - _validatorIndexes[_consensusAddr] = _candidateIdx; - _candidate.consensusAddr = _consensusAddr; - } - - _candidate.candidateAdmin = _stakingAddr; - _candidate.treasuryAddr = _treasuryAddr; - _candidate.commissionRate = _commissionRate; - _candidate.stakedAmount = _amount; - - _globalBalanceChanged = true; - - emit ValidatorProposed(_consensusAddr, _stakingAddr, _amount, _candidate); - } - - /** - * @dev See {IStaking-stake}. - */ - function stake(address _consensusAddr) external payable { - uint256 _amount = msg.value; - (, ValidatorCandidate storage _candidate) = _getCandidate(_consensusAddr); - console.log("[*] Staking"); - console.log("[ ] \t stake address:\t", _candidate.candidateAdmin); - console.log("[ ] \t consensus address:\t", _candidate.consensusAddr); - require(_candidate.candidateAdmin == msg.sender, "Staking: invalid staking address"); - - _candidate.stakedAmount += _amount; - - _globalBalanceChanged = true; - - emit Staked(_consensusAddr, _amount); - } - - /** - * @dev See {IStaking-unstake}. - */ - function unstake(address _consensusAddr, uint256 _amount) external { - (, ValidatorCandidate storage _candidate) = _getCandidate(_consensusAddr); - require(_candidate.candidateAdmin == msg.sender, "Staking: caller must be staking address"); - uint256 _maxUnstake = _candidate.stakedAmount - minValidatorBalance; - require(_amount <= _maxUnstake, "Staking: invalid staked amount left"); - - _candidate.stakedAmount -= _amount; - _transferRON(msg.sender, _amount); - - _globalBalanceChanged = true; - - emit Unstaked(_consensusAddr, _amount); - } - - /** - * @notice Allow validator sends renouncing request. The request gets affected when the epoch ends. - * - * @dev The following procedure must be done in multiple methods in order to finish the renounce. - * - * 1. This method: - * - Set `ValidatorState` of validator to `ON_REQUESTING_RENOUNCE`; - * - Push the validator to a pending list; - * - Trigger the `_globalBalanceChanged` flag. - * - * 2. The `updateValidatorSet` method: - * - Set `ValidatorState` of validator to `ON_CONFIRMED_RENOUNCE`; - * - Set the balance-to-sort of the validator to `0`; - * - Reset the `_globalBalanceChanged`s flag. - * - * 3. The `finalizeRenouncingValidator` method: - * - Remove validator from pending list - * - Set `ValidatorState` of validator to `RENOUNCED`; - * - Set `stakedAmount` of validator to `0`; - * - Transfer the staked to the respective staking address. - * - * Requirements: - * - The validator must be exist - * - The validator must be in `ACTIVE` state - * - */ - function requestRenouncingValidator(address _consensusAddr) external { - (uint256 _index, ValidatorCandidate storage _candidate) = _getCandidate(_consensusAddr); - require(_candidate.candidateAdmin == msg.sender, "Staking: caller must be staking address"); - - console.log("[*] requestRenouncingValidator"); - console.log("[ ] \t index", _index); - - _candidate.state = ValidatorState.ON_REQUESTING_RENOUNCE; - _pendingRenouncingValidatorIndexes.push(_index); - - _globalBalanceChanged = true; - - emit ValidatorRenounceRequested(_consensusAddr, _candidate.stakedAmount); - } - - /** - * @notice Allow validator finalizes renouncing request. - * - * @dev For the logic of this method, refer to {IStaking-requestRenouncingValidator} - * - * Requirements: - * - The validator must be exist - * - The validator must submitted renouncing request, and be `ON_CONFIRMED_RENOUNCE` state - */ - function finalizeRenouncingValidator(address _consensusAddr) external { - (uint256 _index, ValidatorCandidate storage _candidate) = _getDeprecatedCandidate(_consensusAddr); - require(_candidate.candidateAdmin == msg.sender, "Staking: caller must be staking address"); - require( - _candidate.state == ValidatorState.ON_CONFIRMED_RENOUNCE, - "Staking: validator state is not ON_CONFIRMED_RENOUNCE" - ); - - bool _found; - uint _length = _pendingRenouncingValidatorIndexes.length; - uint _amount = _candidate.stakedAmount; - for (uint i; i < _length; ++i) { - if (_index == _pendingRenouncingValidatorIndexes[i]) { - _found = true; - _pendingRenouncingValidatorIndexes[i] = _pendingRenouncingValidatorIndexes[_length - 1]; - _pendingRenouncingValidatorIndexes.pop(); - break; - } - } - require(_found, "Staking: cannot finalize not requested renounce"); - - _candidate.state = ValidatorState.RENOUNCED; - _candidate.stakedAmount = 0; - _transferRON(msg.sender, _amount); - - emit ValidatorRenounceFinalized(_consensusAddr, _amount); - } - - /** - * @dev See {IStaking-delegate}. - */ - function delegate(address _consensusAddr) public payable { - _delegate(msg.sender, _consensusAddr, msg.value); - } - - /** - * @dev See {IStaking-delegate}. - */ - function undelegate(address _consensusAddr, uint256 _amount) public { - _undelegate(msg.sender, _consensusAddr, _amount); - _transferRON(msg.sender, _amount); - } - - /** - * @dev TODO: move to IStaking.sol - */ - function redelegate( - address _consensusAddrSrc, - address _consensusAddrDst, - uint256 _amount - ) external { - _undelegate(msg.sender, _consensusAddrSrc, _amount); - _delegate(msg.sender, _consensusAddrDst, _amount); - } - - /** - * @dev TODO - */ - function getRewards(address[] calldata _consensusAddrList) - external - view - returns (uint256[] memory _pending, uint256[] memory _claimable) - { - revert("Unimplemented"); - } - - /** - * @dev Claims rewards. - */ - function claimRewards(address[] calldata _consensusAddrList, uint256 _amounts) external returns (uint256 _amount) { - revert("Unimplemented"); - } - - /** - * @dev Claims the rewards and delegates them to the consensus address. - */ - function delegateRewards(address[] calldata _consensusAddrList, address _consensusAddrDst) - external - returns (uint256 _amount) - { - revert("Unimplemented"); - } - - /** - * @dev Delegates from a validator address. - * - * Requirements: - * - The validator is an existed candidate. - * - * Emits the `Delegated` event. - * - * @notice This function does not verify the `msg.value` with the amount. - */ - function _delegate( - address _delegator, - address _consensusAddr, - uint256 _amount - ) internal { - (, ValidatorCandidate storage _candidate) = _getCandidate(_consensusAddr); - _candidate.delegatedAmount += _amount; - delegatedAmount[_delegator][_consensusAddr] += _amount; - - emit Delegated(_delegator, _consensusAddr, _amount); - } - - /** - * @dev Undelegates from a validator address. - * - * Requirements: - * - The validator is an existed candidate. - * - The delegated amount is larger than or equal to the undelegated amount. - * - * Emits the `Undelegated` event. - * - * @notice Consider transferring back the amount of RON after calling this function. - */ - function _undelegate( - address _delegator, - address _consensusAddr, - uint256 _amount - ) internal { - require(delegatedAmount[_delegator][_consensusAddr] >= _amount, "Staking: insufficient amount to undelegate"); - - (, ValidatorCandidate storage _candidate) = _getDeprecatedCandidate(_consensusAddr); - _candidate.delegatedAmount -= _amount; - delegatedAmount[_delegator][_consensusAddr] -= _amount; - - emit Undelegated(_delegator, _consensusAddr, _amount); - } - - /** - * @dev Returns the active validator candidate from storage. - * - * Requirements: - * - The candidate is already existed. - * - The candidate is in ACTIVE state - * - */ - function _getCandidate(address _consensusAddr) - internal - view - returns (uint256 index_, ValidatorCandidate storage candidate_) - { - (index_, candidate_) = _getDeprecatedCandidate(_consensusAddr); - require(candidate_.state == ValidatorState.ACTIVE, "Staking: query for deprecated candidate"); - } - - /** - * @dev Returns the validator candidate from storage, without checking his status. - * - * Requirements: - * - The candidate is already existed. - * - */ - function _getDeprecatedCandidate(address _consensusAddr) - internal - view - returns (uint256 index_, ValidatorCandidate storage candidate_) - { - index_ = _validatorIndexes[_consensusAddr]; - require(index_ > 0, "Staking: query for nonexistent candidate"); - console.log("[*] _getCandidate"); - console.log("[ ] \t consensus address:\t", _consensusAddr); - console.log("[ ] \t index:\t", index_); - candidate_ = validatorCandidates[index_]; - } - - function _transferRON(address _to, uint256 _amount) private { - // Using `call` to remove 2300 gas stipend - // https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/ - (bool _success, ) = _to.call{ value: _amount }(""); - require(_success, "Staking: transfer RON failed"); - } - - /** - * @notice Update set of validators - * - * @dev Sorting the validators by their current balance, then pick the top N validators to be - * assigned to the new set. The result is returned to the `ValidatorSet` contract. - * - * Requirements: - * - Only `ValidatorSet` contract can call this function - * - * @return newValidatorSet Validator set for the new epoch - */ - function updateValidatorSet() external onlyValidatorSetContract returns (address[] memory newValidatorSet) { - console.log("[ ] \t 1. gas left", gasleft()); - /// checking global state, skipping sorting if unchanged - console.log("[*] updateValidatorSet"); - if (!_globalBalanceChanged) { - console.log("[ ] \t skipped"); - return getCurrentValidatorSet(); - } - - console.log("[ ] \t sorted"); - _globalBalanceChanged = false; - - /// update renouncing status - for (uint i = 0; i < _pendingRenouncingValidatorIndexes.length; ++i) { - console.log("[ ] \t updating renouncing", i, _pendingRenouncingValidatorIndexes[i]); - ValidatorCandidate storage _renouncingValidator = validatorCandidates[_pendingRenouncingValidatorIndexes[i]]; - _renouncingValidator.state = ValidatorState.ON_CONFIRMED_RENOUNCE; - } - - /// prepare sorting data - uint _length = validatorCandidates.length; - uint _numOfActiveNodes = 0; - Sorting.Node[] memory _nodes = new Sorting.Node[](_length); - Sorting.Node[] memory _sortedNodes = new Sorting.Node[](_length); - - console.log("[ ] \t prepare data"); - _nodes[0] = Sorting.Node(0, type(uint256).max); - for (uint i = 1; i < _length; i++) { - ValidatorCandidate storage _candidate = validatorCandidates[i]; - _nodes[i].key = i; - if (_candidate.state == ValidatorState.ACTIVE) { - _nodes[i].value = _candidate.stakedAmount + _candidate.delegatedAmount; - _numOfActiveNodes++; - } - console.log("[ ] \t\t key, value \t\t", _nodes[i].key, "\t", _nodes[i].value); - } - - console.log("[ ] \t 3. gas left", gasleft()); - /// do sort - _sortedNodes = Sorting.sortNodes(_nodes); - - console.log("[ ] \t 4. gas left", gasleft()); - - /// TODO(bao): pick M validators which are governance - uint _currentSetSize = (_numOfActiveNodes < numOfCabinets) ? (_numOfActiveNodes + 1) : (numOfCabinets + 1); - console.log("[ ] \t after sort"); - console.log("[ ] \t\t _currentSetSize", _currentSetSize); - delete _currentValidatorIndexes; - for (uint i = 0; i < _currentSetSize; i++) { - console.log("[ ] \t\t key, value \t\t", _sortedNodes[i].key, "\t", _sortedNodes[i].value); - _currentValidatorIndexes.push(_sortedNodes[i].key); - } - - console.log("[ ] \t 5. gas left", gasleft()); - - return getCurrentValidatorSet(); - } - - /////////////////////////////////////////////////////////////////////////////////////// - // FUNCTIONS FOR QUERYING // - /////////////////////////////////////////////////////////////////////////////////////// - - function getCurrentValidatorSet() public view returns (address[] memory currentValidatorSet_) { - console.log("[*] getPendingRenouncingValidatorIndexes"); - uint _length = _currentValidatorIndexes.length; - currentValidatorSet_ = new address[](_length); - for (uint i = 0; i < _length; i++) { - console.log("[ ] \t _i, _currentIndexes[i]", i, _currentValidatorIndexes[i]); - currentValidatorSet_[i] = validatorCandidates[_currentValidatorIndexes[i]].consensusAddr; - } - return currentValidatorSet_; - } - - function getPendingRenouncingValidatorIndexes() public view returns (uint[] memory pendingRenouncingIndexes_) { - uint _length = _pendingRenouncingValidatorIndexes.length; - pendingRenouncingIndexes_ = new uint[](_length); - for (uint i = 0; i < _length; i++) { - pendingRenouncingIndexes_[i] = _pendingRenouncingValidatorIndexes[i]; - } - } - - function governanceAdminContract() external view override returns (address) {} - - function setMinValidatorBalance(uint256) external override {} - - function maxValidatorCandidate() external view override returns (uint256) {} - - function setMaxValidatorCandidate(uint256) external override {} - - function getValidatorCandidates() external view override returns (ValidatorCandidate[] memory candidates) {} - - function recordReward(address _consensusAddr, uint256 _reward) external payable override {} - - function settleRewardPools(address[] calldata _consensusAddrs) external override {} - - function sinkPendingReward(address _consensusAddr) external override {} - - function deductStakingAmount(address _consensusAddr, uint256 _amount) external override {} - - function renounce(address consensusAddr) external override {} - - function getRewards(address _user, address[] calldata _poolAddrList) - external - view - override - returns (uint256[] memory _pendings, uint256[] memory _claimables) - {} - - function claimRewards(address[] calldata _consensusAddrList) external override returns (uint256 _amount) {} - - function getCandidateWeights() - external - view - override - returns (address[] memory _candidates, uint256[] memory _weights) - {} - - function commissionRateOf(address _consensusAddr) external view override returns (uint256 _rate) {} - - function treasuryAddressOf(address _consensusAddr) external view override returns (address) {} -} diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 330b67a57..1d59fc24e 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -77,7 +77,9 @@ abstract contract StakingManager is IStaking, RewardCalculation { /** * @inheritdoc IStaking */ - function renounce(address consensusAddr) external { + function renounce( + address /* _consensusAddr */ + ) external { revert("unimplemented"); } diff --git a/contracts/validator/ValidatorSet.sol b/contracts/validator/ValidatorSet.sol deleted file mode 100644 index dd66bc350..000000000 --- a/contracts/validator/ValidatorSet.sol +++ /dev/null @@ -1,193 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import "../interfaces/ISlashIndicator.sol"; -import "../interfaces/IStaking.sol"; -import "../interfaces/IValidatorSet.sol"; -import "./ValidatorSetCore.sol"; - -/** - * @title Set of validators in current epoch - * @notice This contract maintains set of validator in the current epoch of Ronin network - */ -contract ValidatorSet is IValidatorSet, ValidatorSetCore { - using EnumerableMap for EnumerableMap.AddressToUintMap; - - uint256 private constant INIT_NUM_OF_CABINETS = 21; - uint256 private constant INIT_EPOCH_LENGTH = 200; - - uint256 public numOfCabinets; - uint256 public epochLength; - uint256 public lastUpdated; - - IStaking stakingContract; - ISlashIndicator slashContract; - - uint256 public numberOfEpochsInPeriod = 0; - uint256 public numberOfBlocksInEpoch = 0; - - event ValidatorSetUpdated(); - event ValidatorJailed(address indexed validator); - /// @dev Event for each time the validators deposit mining reward - event ValidatorDeposited(address indexed validator, uint256 amount); - /// @dev Event for the in-jail validators deposit mining reward - event DeprecatedDeposit(address indexed validator, uint256 amount); - - modifier onlyCoinbase() { - require(tx.origin == block.coinbase, "Validators: Only coinbase"); - _; - } - - modifier onlyValidator() { - require(isValidator(msg.sender), "Validators: Only validator"); - _; - } - - modifier noEmptyDeposit() { - require(msg.value > 0, "Validators: No empty deposit"); - _; - } - - modifier atEpochEnding() { - require((block.number + 1) % epochLength == 0, "Validators: Only at the end of the epoch"); - _; - } - - constructor(IStaking _stakingContract, ISlashIndicator _slashContract) { - stakingContract = _stakingContract; - slashContract = _slashContract; - numOfCabinets = INIT_NUM_OF_CABINETS; - epochLength = INIT_EPOCH_LENGTH; - } - - function updateValidators() external onlyValidator atEpochEnding returns (address[] memory) { - // 1. fetch new validator set from staking contract - IStaking.ValidatorCandidate[] memory _upcommingValidatorSet; // = stakingContract.updateValidatorSet(); - require(_upcommingValidatorSet.length <= numOfCabinets, "Validators: Exceeds maximum validators per epoch"); - - // 2. update last updated - lastUpdated = block.number; - - // 3. update new validator set - _doUpdateState(_upcommingValidatorSet); - emit ValidatorSetUpdated(); - - // 4. return new validator set - return getValidators(); - } - - function getLastUpdated() external view returns (uint256 height) { - return lastUpdated; - } - - function depositReward() external payable onlyCoinbase { - uint256 _value = msg.value; - address _valAddr = tx.origin; - Validator storage _validator = _getValidator(_valAddr); - - // 1. check if validator is in current epoch - if (_isInCurrentValidatorSet(_valAddr)) { - // 2. check if validator is in jail - if (_validator.jailed) { - emit DeprecatedDeposit(_valAddr, _value); - } else { - stakingContract.recordReward(_valAddr, _value); - } - } - - // 3. if get deposit from deprecated validators - emit DeprecatedDeposit(_valAddr, _value); - } - - function slashMisdemeanor(address validator) external { - revert("Unimplemented"); - } - - /// TODO: - function slashFelony(address validator) external { - revert("Unimplemented"); - } - - /// TODO: - function slashDoubleSign(address validator) external { - revert("Unimplemented"); - } - - function updateNumOfCabinets(uint256 _numOfCabinets) external onlyCoinbase { - revert("Unimplemented"); - } - - function updateEpochLength(uint256 _epochLength) external onlyCoinbase { - revert("Unimplemented"); - } - - function getValidators() public view returns (address[] memory) { - uint256 size = _getCurrentValidatorSetSize(); - address[] memory validatorAddresses = new address[](size); - for (uint256 i = 0; i < size; i++) { - Validator memory _v = _getValidatorAtMiningIndex(i); - validatorAddresses[i] = _v.consensusAddr; - } - return validatorAddresses; - } - - function isValidator(address _addr) public view returns (bool) { - return validatorSetMap[_addr] > 0; - } - - function isWorkingValidator(address _addr) public view returns (bool) { - Validator memory _v = _getValidator(_addr); - return (!_v.jailed); - } - - function isCurrentValidator(address _addr) public view returns (bool) { - return _isInCurrentValidatorSet(_addr); - } - - /// - /// INTERNAL FUNCTIONS - /// - - function _doUpdateState(IStaking.ValidatorCandidate[] memory _incomingValidatorSet) private { - uint256 n = _getCurrentValidatorSetSize(); - uint256 m = _incomingValidatorSet.length; - uint256 k = n < m ? n : m; - - // keep the validators that already in the exact order and update wrong validators - // incoming set: [ ========= ] - // current set: [ ===== ] - // updating part: ^^^^^ - for (uint256 i = 0; i < k; ++i) { - Validator memory _oldValidator = _getValidatorAtMiningIndex(i); - if (!_isSameValidator(_incomingValidatorSet[i], _oldValidator)) { - _setValidatorAtMiningIndex(i, _incomingValidatorSet[i]); - } - } - - // in case the incoming set is longer, update the set by appending to the current set - // incoming set: [ ========= ] - // current set: [ ===== ] - // updating part: ^^^^ - if (m > n) { - for (uint256 i = n; i < m; ++i) { - _setValidatorAtMiningIndex(i, _incomingValidatorSet[i]); - } - } - - // in case the current set is longer, delete trailing members in the current set - // incoming set: [ === ] - // current set: [ ========= ] - // updating part: ^^^^^^ - if (n > m) { - for (uint256 i = m; i < n; ++i) { - _popValidatorFromMiningIndex(); - } - } - } - - function periodOf(uint256 _block) external view override returns (uint256) { - return _block / numberOfEpochsInPeriod / numberOfBlocksInEpoch + 1; - } -} diff --git a/contracts/validator/ValidatorSetCore.sol b/contracts/validator/ValidatorSetCore.sol deleted file mode 100644 index bca9e794e..000000000 --- a/contracts/validator/ValidatorSetCore.sol +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import "../interfaces/IValidatorSet.sol"; -import "../interfaces/IStaking.sol"; - -/** - * @title Set of validators in current epoch - * @notice This contract maintains set of validator in the current epoch of Ronin network - */ -abstract contract ValidatorSetCore { - using EnumerableMap for EnumerableMap.AddressToUintMap; - - /// @dev Array of all validators. - /// - /// The element at 0-slot is reserved for unknown validator. Index of each validator must be - /// immutable, since this array is synced per each epoch from the Staking contract. - IValidatorSet.Validator[] public validatorSet; - - /// @dev Map of array of all validators - mapping(address => uint) public validatorSetMap; - - /// @dev Enumerable map of indexes of the current validator set. - /// - /// The array of [0, 3, 4, 1] means the sequence of validators to mine block in the next epoch - /// will be at the 3-, 4- and 1-index, first element (0-index) is ignored. One can query the - /// mining order of an arbitrary address by this enumerable map. This array is indexed from 1, - /// and it contains non-zero value, due to reserved 0-slot in the `validatorSet` array. - uint[] internal currentValidatorIndexes; - mapping(address => uint) currentValidatorIndexesMap; - - constructor() { - // Add empty validator at 0-slot for the set map - validatorSet.push(); - // Add reserved 0-index for the current validator set - currentValidatorIndexes.push(); - } - - function _isInCurrentValidatorSet(address _addr) internal view returns (bool) { - return (currentValidatorIndexesMap[_addr] != 0); - } - - function _getCurrentValidatorSetSize() internal view returns (uint) { - return currentValidatorIndexes.length - 1; - } - - function _setValidatorAtMiningIndex(uint _miningIndex, IStaking.ValidatorCandidate memory _incomingValidator) - internal - { - require(_miningIndex > 0, "Validator: Cannot set at 0-index of mining set"); - uint _length = currentValidatorIndexes.length; - require(_miningIndex <= _length, "Validator: Cannot set at out-of-bound mining set"); - - address _newValAddr = _incomingValidator.consensusAddr; - uint _incomingIndex = _setValidator(_incomingValidator, false); - - currentValidatorIndexesMap[_newValAddr] = _miningIndex; - if (_miningIndex < _length) { - currentValidatorIndexes[_miningIndex] = _incomingIndex; - } else { - currentValidatorIndexes.push(_incomingIndex); - } - } - - /** - * @return validatorIndex_ Actual index of the validator in the `validatorSet` - */ - function _setValidator(IStaking.ValidatorCandidate memory _incomingValidator, bool _forcedIfExist) - internal - returns (uint validatorIndex_) - { - (bool _success, IValidatorSet.Validator storage _currentValidator, uint _actualIndex) = _tryGetValidator( - _incomingValidator.consensusAddr - ); - - if (!_success) { - return __setUnexistentValidator(_incomingValidator); - } else { - if (_forcedIfExist) { - return __setExistedValidator(_incomingValidator, _currentValidator); - } else { - return _actualIndex; - } - } - } - - function _popValidatorFromMiningIndex() internal { - uint _length = currentValidatorIndexes.length - 1; - require(_length > 1, "Validator: Cannot remove the last element"); - - IValidatorSet.Validator storage _lastValidatorInEpoch = validatorSet[currentValidatorIndexes[_length]]; - currentValidatorIndexesMap[_lastValidatorInEpoch.consensusAddr] = 0; - currentValidatorIndexes.pop(); - } - - function _getValidatorAtMiningIndex(uint _miningIndex) internal view returns (IValidatorSet.Validator storage) { - require(_miningIndex < currentValidatorIndexes.length, "Validator: No validator exists at queried mining index"); - uint _actualIndex = currentValidatorIndexes[_miningIndex]; - require(_actualIndex != 0, "Validator: No validator exists at mining index 0"); - return validatorSet[_actualIndex]; - } - - function _getValidator(address _valAddr) internal view returns (IValidatorSet.Validator storage) { - (bool _success, IValidatorSet.Validator storage _v, ) = _tryGetValidator(_valAddr); - require(_success, string(abi.encodePacked("Validator: Nonexistent validator ", _valAddr))); - return _v; - } - - function _tryGetValidator(address _valAddr) - internal - view - returns ( - bool, - IValidatorSet.Validator storage, - uint - ) - { - uint _actualIndex = validatorSetMap[_valAddr]; - IValidatorSet.Validator storage _v = validatorSet[_actualIndex]; - if (_actualIndex == 0) { - return (false, _v, 0); - } - return (true, _v, _actualIndex); - } - - function _isSameValidator(IStaking.ValidatorCandidate memory _v1, IValidatorSet.Validator memory _v2) - internal - pure - returns (bool) - { - return _v1.consensusAddr == _v2.consensusAddr && _v1.treasuryAddr == _v2.treasuryAddr; - } - - function __setExistedValidator( - IStaking.ValidatorCandidate memory _incomingValidator, - IValidatorSet.Validator storage _currentValidator - ) private returns (uint) { - _currentValidator.consensusAddr = _incomingValidator.consensusAddr; - _currentValidator.treasuryAddr = _incomingValidator.treasuryAddr; - - return validatorSetMap[_currentValidator.consensusAddr]; - } - - function __setUnexistentValidator(IStaking.ValidatorCandidate memory _incomingValidator) private returns (uint) { - uint _actualIndex = validatorSet.length; - - validatorSetMap[_incomingValidator.consensusAddr] = _actualIndex; - IValidatorSet.Validator storage _v = validatorSet.push(); - _v.consensusAddr = _incomingValidator.consensusAddr; - _v.treasuryAddr = _incomingValidator.treasuryAddr; - - return _actualIndex; - } -} diff --git a/src/config.ts b/src/config.ts index 3ee9e1886..98c376841 100644 --- a/src/config.ts +++ b/src/config.ts @@ -35,6 +35,9 @@ export interface SlashIndicatorConf { | { misdemeanorThreshold: BigNumberish; felonyThreshold: BigNumberish; + slashFelonyAmount: BigNumberish; + slashDoubleSignAmount: BigNumberish; + felonyJailBlocks: BigNumberish; } | undefined; } @@ -45,8 +48,6 @@ export interface RoninValidatorSetConf { maxValidatorNumber: BigNumberish; numberOfBlocksInEpoch: BigNumberish; numberOfEpochsInPeriod: BigNumberish; - slashFelonyAmount: BigNumberish; - slashDoubleSignAmount: BigNumberish; } | undefined; } @@ -74,10 +75,14 @@ export const slashIndicatorConf: SlashIndicatorConf = { [Network.Devnet]: { misdemeanorThreshold: 50, felonyThreshold: 150, + slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), // 10 RON + slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), // 10 RON + felonyJailBlocks: 28800 * 2, // jails for 2 days }, [Network.Testnet]: undefined, [Network.Mainnet]: undefined, }; + // TODO: update config for devnet, testnet & mainnet export const roninValidatorSetConf: RoninValidatorSetConf = { [Network.Hardhat]: undefined, @@ -85,8 +90,6 @@ export const roninValidatorSetConf: RoninValidatorSetConf = { maxValidatorNumber: 21, numberOfBlocksInEpoch: 600, numberOfEpochsInPeriod: 48, // 1 day - slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), // 10 RON - slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), // 10 RON }, [Network.Testnet]: undefined, [Network.Mainnet]: undefined, diff --git a/src/deploy/proxy/ronin-validator-proxy.ts b/src/deploy/proxy/ronin-validator-proxy.ts index 377a4b7e0..83ff95f45 100644 --- a/src/deploy/proxy/ronin-validator-proxy.ts +++ b/src/deploy/proxy/ronin-validator-proxy.ts @@ -17,8 +17,6 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme roninValidatorSetConf[network.name]!.maxValidatorNumber, roninValidatorSetConf[network.name]!.numberOfBlocksInEpoch, roninValidatorSetConf[network.name]!.numberOfEpochsInPeriod, - roninValidatorSetConf[network.name]!.slashFelonyAmount, - roninValidatorSetConf[network.name]!.slashDoubleSignAmount, ]); await deploy('RoninValidatorSetProxy', { diff --git a/src/deploy/proxy/slash-indicator-proxy.ts b/src/deploy/proxy/slash-indicator-proxy.ts index 5e25c31eb..218592aef 100644 --- a/src/deploy/proxy/slash-indicator-proxy.ts +++ b/src/deploy/proxy/slash-indicator-proxy.ts @@ -14,6 +14,9 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme slashIndicatorConf[network.name]!.misdemeanorThreshold, slashIndicatorConf[network.name]!.felonyThreshold, initAddress[network.name]!.validatorContract, + slashIndicatorConf[network.name]!.slashFelonyAmount, + slashIndicatorConf[network.name]!.slashDoubleSignAmount, + slashIndicatorConf[network.name]!.felonyJailBlocks, ]); await deploy('SlashIndicatorProxy', { diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index 434b7fa48..84261f1aa 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -5,9 +5,9 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { SlashIndicator, SlashIndicator__factory, - MockEmptyValidatorSet, - MockEmptyValidatorSet__factory, TransparentUpgradeableProxy__factory, + MockValidatorSet__factory, + MockValidatorSet, } from '../../src/types'; import { Address } from 'hardhat-deploy/dist/types'; @@ -15,7 +15,7 @@ let slashContract: SlashIndicator; let deployer: SignerWithAddress; let proxyAdmin: SignerWithAddress; -let dummyValidatorsContract: MockEmptyValidatorSet; +let validatorContract: MockValidatorSet; let vagabond: SignerWithAddress; let coinbases: SignerWithAddress[]; let defaultCoinbase: Address; @@ -62,15 +62,21 @@ describe('Slash indicator test', () => { localIndicators = Array(coinbases.length).fill(0); - dummyValidatorsContract = await new MockEmptyValidatorSet__factory(deployer).deploy(); + const nonce = await deployer.getTransactionCount(); + const slashIndicatorAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); + validatorContract = await new MockValidatorSet__factory(deployer).deploy( + ethers.constants.AddressZero, + slashIndicatorAddr, + 0, + 0 + ); const logicContract = await new SlashIndicator__factory(deployer).deploy(); const proxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( logicContract.address, proxyAdmin.address, - logicContract.interface.encodeFunctionData('initialize', [50, 150, dummyValidatorsContract.address]) + logicContract.interface.encodeFunctionData('initialize', [10, 20, validatorContract.address, 0, 0, 28800 * 2]) ); slashContract = SlashIndicator__factory.connect(proxyContract.address, deployer); - await dummyValidatorsContract.connect(deployer).setSlashingContract(slashContract.address); let thresholds = await slashContract.getSlashThresholds(); felonyThreshold = thresholds[0].toNumber(); @@ -198,7 +204,7 @@ describe('Slash indicator test', () => { await resetCoinbase(); - tx = await dummyValidatorsContract.resetCounters([coinbases[slasheeIdx].address]); + tx = await validatorContract.resetCounters([coinbases[slasheeIdx].address]); expect(tx).to.emit(slashContract, 'UnavailabilityIndicatorReset').withArgs(coinbases[slasheeIdx].address); await resetLocalCounterForValidatorAt(slasheeIdx); @@ -225,7 +231,7 @@ describe('Slash indicator test', () => { await resetCoinbase(); - tx = await dummyValidatorsContract.resetCounters(slasheeIdxs.map((_) => coinbases[_].address)); + tx = await validatorContract.resetCounters(slasheeIdxs.map((_) => coinbases[_].address)); for (let j = 0; j < slasheeIdxs.length; j++) { expect(tx).to.emit(slashContract, 'UnavailabilityIndicatorReset').withArgs(coinbases[slasheeIdxs[j]].address); diff --git a/test/staking/DPoStaking.test.ts b/test/staking/DPoStaking.test.ts index b53f30ee5..9f507040d 100644 --- a/test/staking/DPoStaking.test.ts +++ b/test/staking/DPoStaking.test.ts @@ -4,8 +4,8 @@ import { BigNumber, BigNumberish } from 'ethers'; import { ethers, network } from 'hardhat'; import { DPoStaking, DPoStaking__factory, TransparentUpgradeableProxy__factory } from '../../src/types'; -import { MockValidatorSetForStaking__factory } from '../../src/types/factories/MockValidatorSetForStaking__factory'; -import { MockValidatorSetForStaking } from '../../src/types/MockValidatorSetForStaking'; +import { MockValidatorSet__factory } from '../../src/types/factories/MockValidatorSet__factory'; +import { MockValidatorSet } from '../../src/types/MockValidatorSet'; const EPS = 1; @@ -16,7 +16,7 @@ let proxyAdmin: SignerWithAddress; let userA: SignerWithAddress; let userB: SignerWithAddress; let governanceAdmin: SignerWithAddress; -let validatorContract: MockValidatorSetForStaking; +let validatorContract: MockValidatorSet; let stakingContract: DPoStaking; let validatorCandidates: SignerWithAddress[]; @@ -95,7 +95,12 @@ describe('DPoStaking test', () => { validatorCandidates = validatorCandidates.slice(0, 3); const nonce = await deployer.getTransactionCount(); const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); - validatorContract = await new MockValidatorSetForStaking__factory(deployer).deploy(stakingContractAddr, 10, 2); + validatorContract = await new MockValidatorSet__factory(deployer).deploy( + stakingContractAddr, + ethers.constants.AddressZero, + 10, + 2 + ); await validatorContract.deployed(); const logicContract = await new DPoStaking__factory(deployer).deploy(); await logicContract.deployed(); diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts deleted file mode 100644 index b764b3701..000000000 --- a/test/staking/Staking.test.ts +++ /dev/null @@ -1,528 +0,0 @@ -import { expect } from 'chai'; -import { ethers, deployments } from 'hardhat'; -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; - -import { Staking, Staking__factory } from '../../src/types'; -import { ValidatorCandidateStruct } from '../../src/types/IStaking'; -import { BigNumber } from 'ethers'; -import { DEFAULT_ADDRESS } from '../../src/utils'; - -let stakingContract: Staking; - -let signers: SignerWithAddress[]; -let admin: SignerWithAddress; -let candidates: ValidatorCandidateStruct[] = []; -let stakingAddrs: SignerWithAddress[] = []; -let consensusAddrs: SignerWithAddress[] = []; -let treasuryAddrs: SignerWithAddress[] = []; - -enum ValidatorStateEnum { - ACTIVE = 0, - ON_REQUESTING_RENOUNCE = 1, - ON_CONFIRMED_RENOUNCE = 2, - RENOUNCED = 3, -} - -const generateCandidate = ( - candidateAdmin: string, - consensusAddr: string, - treasuryAddr: string -): ValidatorCandidateStruct => { - return { - candidateAdmin: candidateAdmin, - consensusAddr: consensusAddr, - treasuryAddr: treasuryAddr, - commissionRate: 0, - stakedAmount: 0, - state: ValidatorStateEnum.ACTIVE, - delegatedAmount: 0, - governing: false, - ____gap: Array.apply(null, Array(20)).map((_) => 0), - }; -}; - -const validateTwoObjects = async (definedObj: any, resultObj: any) => { - let key: keyof typeof definedObj; - for (key in definedObj) { - const definedVal = definedObj[key]; - const resultVal = resultObj[key]; - - if (Array.isArray(resultVal)) { - for (let i = 0; i < resultVal.length; i++) { - await expect(resultVal[i]).to.eq(definedVal[i]); - } - } else { - await expect(resultVal).to.eq(definedVal); - } - } -}; - -describe.skip('Staking test', () => { - let unstakingOnHoldBlocksNum: BigNumber; - let minValidatorBalance: BigNumber; - - before(async () => { - [admin, ...signers] = await ethers.getSigners(); - - console.log('Init addresses for 21 candidates...'); - for (let i = 0; i < 21; i++) { - candidates.push( - generateCandidate(signers[3 * i].address, signers[3 * i + 1].address, signers[3 * i + 2].address) - ); - stakingAddrs.push(signers[3 * i]); - consensusAddrs.push(signers[3 * i + 1]); - treasuryAddrs.push(signers[3 * i + 2]); - - console.log( - i, - signers[3 * i].address, - signers[3 * i + 1].address, - signers[3 * i + 2].address, - (await ethers.provider.getBalance(signers[3 * i].address)).toString(), - (await ethers.provider.getBalance(signers[3 * i + 1].address)).toString(), - (await ethers.provider.getBalance(signers[3 * i + 2].address)).toString() - ); - } - }); - - describe('Single flow', async () => { - describe('Validator functions', async () => { - before(async () => { - await deployments.fixture('StakingContract'); - const stakingProxyDeployment = await deployments.get('StakingProxy'); - stakingContract = Staking__factory.connect(stakingProxyDeployment.address, admin); - - unstakingOnHoldBlocksNum = await stakingContract.unstakingOnHoldBlocksNum(); - minValidatorBalance = await stakingContract.minValidatorBalance(); - - console.log('Set validator set contract...'); - await stakingContract.setValidatorSetContract(admin.address); - }); - - describe('Proposing', async () => { - it('Should be able to propose 1 validator', async () => { - let tx = await stakingContract - .connect(stakingAddrs[0]) - .proposeValidator(consensusAddrs[0].address, treasuryAddrs[0].address, 0, { - value: minValidatorBalance.toString(), - }); - expect(await tx) - .to.emit(stakingContract, 'ValidatorProposed') - .withArgs(consensusAddrs[0].address, stakingAddrs[0].address, minValidatorBalance, candidates[0]); - }); - - it('Should not be able to propose 1 validator - insufficient fund', async () => { - let tx = stakingContract - .connect(stakingAddrs[1]) - .proposeValidator(consensusAddrs[1].address, treasuryAddrs[1].address, 0, { - value: minValidatorBalance.sub(1).toString(), - }); - await expect(tx).to.revertedWith('Staking: insuficient amount'); - }); - - it('Should not be able to propose 1 validator - duplicated validator', async () => { - let tx = stakingContract - .connect(stakingAddrs[0]) - .proposeValidator(consensusAddrs[0].address, treasuryAddrs[0].address, 0, { - value: minValidatorBalance.toString(), - }); - await expect(tx).to.revertedWith('Staking: cannot propose an existed candidate'); - }); - }); - - describe('Staking', async () => { - it('Should be able to stake for a validator', async () => { - let stakingValue = ethers.utils.parseEther('1.0'); - let tx = stakingContract.connect(stakingAddrs[0]).stake(consensusAddrs[0].address, { - value: stakingValue, - }); - await expect(tx).to.emit(stakingContract, 'Staked').withArgs(consensusAddrs[0].address, stakingValue); - }); - - it('Should not be able to stake for an unexistent validator', async () => { - let stakingValue = ethers.utils.parseEther('1.0'); - let tx = stakingContract.connect(stakingAddrs[0]).stake(consensusAddrs[1].address, { - value: stakingValue, - }); - await expect(tx).to.revertedWith('Staking: query for nonexistent candidate'); - }); - }); - - describe('Unstaking', async () => { - it('Should not be able to unstake - exceeds minimum balance', async () => { - let unstakingValue = ethers.utils.parseEther('1.1'); - let tx = stakingContract.connect(stakingAddrs[0]).unstake(consensusAddrs[0].address, unstakingValue); - await expect(tx).to.revertedWith('Staking: invalid staked amount left'); - }); - - it('Should not be able to unstake - caller is not staking address', async () => { - let unstakingValue = ethers.utils.parseEther('1.0'); - let tx = stakingContract.connect(stakingAddrs[1]).unstake(consensusAddrs[0].address, unstakingValue); - await expect(tx).to.revertedWith('Staking: caller must be staking address'); - }); - - it('Should be able to unstake', async () => { - let tx; - let unstakingValue = ethers.utils.parseEther('1.0'); - let descresingValue = ethers.utils.parseEther('-1.0'); - await expect( - () => (tx = stakingContract.connect(stakingAddrs[0]).unstake(consensusAddrs[0].address, unstakingValue)) - ).to.changeEtherBalances([stakingAddrs[0], stakingContract], [unstakingValue, descresingValue]); - await expect(tx).to.emit(stakingContract, 'Unstaked').withArgs(consensusAddrs[0].address, unstakingValue); - }); - - it('Should not be able to unstake - exceeds minimum balance 2', async () => { - let unstakingValue = 1; - let tx = stakingContract.connect(stakingAddrs[0]).unstake(consensusAddrs[0].address, unstakingValue); - await expect(tx).to.revertedWith('Staking: invalid staked amount left'); - }); - }); - - describe('Requesting renounce', async () => { - it('Should not be able to request renounce validator - caller is not staking address', async () => { - let tx = stakingContract.connect(stakingAddrs[1]).requestRenouncingValidator(consensusAddrs[0].address); - await expect(tx).to.revertedWith('Staking: caller must be staking address'); - }); - - it('Should be able to request renounce validator', async () => { - let stakingValue = ethers.utils.parseEther('1.0'); - await stakingContract.connect(stakingAddrs[0]).stake(consensusAddrs[0].address, { - value: stakingValue, - }); - - let tx = stakingContract.connect(stakingAddrs[0]).requestRenouncingValidator(consensusAddrs[0].address); - await expect(tx) - .to.emit(stakingContract, 'ValidatorRenounceRequested') - .withArgs(consensusAddrs[0].address, minValidatorBalance.add(stakingValue)); - }); - - it('Should not be able to request renounce validator twice', async () => { - let tx = stakingContract.connect(stakingAddrs[0]).requestRenouncingValidator(consensusAddrs[0].address); - await expect(tx).to.revertedWith('Staking: query for deprecated candidate'); - }); - - it('Should not be able to propose the validator that is on renounce', async () => { - let tx = stakingContract - .connect(stakingAddrs[0]) - .proposeValidator(consensusAddrs[0].address, treasuryAddrs[0].address, 0, { - value: minValidatorBalance.toString(), - }); - await expect(tx).to.revertedWith('Staking: cannot propose an existed candidate'); - }); - - it('Should not be able to stake for the validator that is on renounce', async () => { - let stakingValue = ethers.utils.parseEther('1.0'); - let tx = stakingContract.connect(stakingAddrs[0]).stake(consensusAddrs[0].address, { - value: stakingValue, - }); - - await expect(tx).to.revertedWith('Staking: query for deprecated candidate'); - }); - - it('Should not be able to unstake for the validator that is on renounce', async () => { - let unstakingValue = ethers.utils.parseEther('1.0'); - let tx = stakingContract.connect(stakingAddrs[1]).unstake(consensusAddrs[0].address, unstakingValue); - await expect(tx).to.revertedWith('Staking: query for deprecated candidate'); - }); - }); - - describe('Finalize renounce', async () => { - it('Should not be able to finalize the renounce - caller is not staking address', async () => { - let tx = stakingContract.connect(stakingAddrs[1]).finalizeRenouncingValidator(consensusAddrs[0].address); - await expect(tx).to.revertedWith('Staking: caller must be staking address'); - }); - - it('Should not be able to finalize renounce - renounce is not confirmed', async () => { - let tx = stakingContract.connect(stakingAddrs[0]).finalizeRenouncingValidator(consensusAddrs[0].address); - await expect(tx).to.revertedWith('Staking: validator state is not ON_CONFIRMED_RENOUNCE'); - }); - - it('Should be able to finalize the renounce', async () => { - expect(await stakingContract.getPendingRenouncingValidatorIndexes()).to.eql([BigNumber.from(1)]); - await stakingContract.connect(admin).updateValidatorSet(); - - let tx; - let incValue = minValidatorBalance.add(ethers.utils.parseEther('1')); - let decValue = BigNumber.from(0).sub(incValue); - await expect( - () => (tx = stakingContract.connect(stakingAddrs[0]).finalizeRenouncingValidator(consensusAddrs[0].address)) - ).to.changeEtherBalances([stakingAddrs[0], stakingContract], [incValue, decValue]); - await expect(tx) - .to.emit(stakingContract, 'ValidatorRenounceFinalized') - .withArgs(consensusAddrs[0].address, incValue); - - expect(await stakingContract.getPendingRenouncingValidatorIndexes()).to.eql([]); - }); - - it('Should not be able to finalize renounce twice', async () => { - let tx = stakingContract.connect(stakingAddrs[0]).finalizeRenouncingValidator(consensusAddrs[0].address); - await expect(tx).to.revertedWith('Staking: validator state is not ON_CONFIRMED_RENOUNCE'); - }); - - it('Should not be able to stake for a renounced validator', async () => { - let stakingValue = ethers.utils.parseEther('1.0'); - let tx = stakingContract.connect(stakingAddrs[0]).stake(consensusAddrs[0].address, { - value: stakingValue, - }); - await expect(tx).to.revertedWith('Staking: query for deprecated candidate'); - }); - - it('Should be able to re-propose the renounced validator, the old index unchanges', async () => { - let tx = await stakingContract - .connect(stakingAddrs[0]) - .proposeValidator(consensusAddrs[0].address, treasuryAddrs[0].address, 0, { - value: minValidatorBalance.toString(), - }); - expect(await tx) - .to.emit(stakingContract, 'ValidatorProposed') - .withArgs(consensusAddrs[0].address, stakingAddrs[0].address, minValidatorBalance, candidates[0]); - - let _expectingCandidate = { - candidateAdmin: stakingAddrs[0].address, - consensusAddr: consensusAddrs[0].address, - treasuryAddr: treasuryAddrs[0].address, - commissionRate: 0, - stakedAmount: minValidatorBalance, - state: ValidatorStateEnum.ACTIVE, - delegatedAmount: 0, - governing: false, - }; - await validateTwoObjects(_expectingCandidate, await stakingContract.validatorCandidates(1)); - }); - }); - - describe('Propose and renounce multiple validator', async () => { - it('Should be able to propose 5 more validator', async () => { - // balance: [3M, 3M+1, 3M+2, 3M+3, 3M+4, 3M+5] - for (let i = 1; i <= 5; ++i) { - let topupValue = minValidatorBalance.add(ethers.utils.parseEther(i.toString())); - let tx = await stakingContract - .connect(stakingAddrs[i]) - .proposeValidator(consensusAddrs[i].address, treasuryAddrs[i].address, i * 100, { - value: topupValue, - }); - expect(await tx) - .to.emit(stakingContract, 'ValidatorProposed') - .withArgs(consensusAddrs[i].address, stakingAddrs[i].address, topupValue, candidates[i]); - } - - await stakingContract.connect(admin).updateValidatorSet(); - let currentSet = await stakingContract.getCurrentValidatorSet(); - let expectingSet = [DEFAULT_ADDRESS].concat([5, 4, 3, 2, 1, 0].map((i) => consensusAddrs[i].address)); - console.log('>>> currentSet', currentSet); - await expect(expectingSet).eql(currentSet); - }); - - describe('Renounce 2 in 6 validator', async () => { - it('Should request to renounce success', async () => { - for (let i = 3; i <= 4; ++i) { - let topoutValue = minValidatorBalance.add(ethers.utils.parseEther(i.toString())); - let tx = stakingContract.connect(stakingAddrs[i]).requestRenouncingValidator(consensusAddrs[i].address); - await expect(tx) - .to.emit(stakingContract, 'ValidatorRenounceRequested') - .withArgs(consensusAddrs[i].address, topoutValue); - } - }); - - it('Should pending list get updated correctly', async () => { - expect(await stakingContract.getPendingRenouncingValidatorIndexes()).to.eql([ - BigNumber.from(4), - BigNumber.from(5), - ]); - }); - - it('Should be update validators list success', async () => { - await stakingContract.connect(admin).updateValidatorSet(); - let currentSet = await stakingContract.getCurrentValidatorSet(); - let expectingSet = [DEFAULT_ADDRESS].concat([5, 2, 1, 0].map((i) => consensusAddrs[i].address)); - console.log('>>> currentSet', currentSet); - await expect(expectingSet).eql(currentSet); - }); - }); - }); - }); - - describe('Delegator functions', async () => {}); - - describe('Updating validator functions', async () => { - beforeEach(async () => { - await deployments.fixture('StakingContract'); - const stakingProxyDeployment = await deployments.get('StakingProxy'); - stakingContract = Staking__factory.connect(stakingProxyDeployment.address, admin); - - unstakingOnHoldBlocksNum = await stakingContract.unstakingOnHoldBlocksNum(); - minValidatorBalance = await stakingContract.minValidatorBalance(); - - console.log('Set validator set contract...'); - await stakingContract.setValidatorSetContract(admin.address); - }); - - it('Should sort 21 validator in descreasing order', async () => { - // balance: [3M+20, 3M+19, 3M+18, 3M+17, 3M+16, 3M+15, ...] - for (let i = 0; i < 21; ++i) { - let topupValue = minValidatorBalance.add(ethers.utils.parseEther((20 - i).toString())); - let tx = await stakingContract - .connect(stakingAddrs[i]) - .proposeValidator(consensusAddrs[i].address, treasuryAddrs[i].address, i * 100, { - value: topupValue, - }); - expect(await tx) - .to.emit(stakingContract, 'ValidatorProposed') - .withArgs(consensusAddrs[i].address, stakingAddrs[i].address, topupValue, candidates[i]); - } - - await stakingContract.connect(admin).updateValidatorSet(); - let currentSet = await stakingContract.getCurrentValidatorSet(); - let expectingSet = [DEFAULT_ADDRESS].concat([...Array(21).keys()].map((i) => consensusAddrs[i].address)); - console.log('>>> currentSet', currentSet); - await expect(expectingSet).eql(currentSet); - }); - - it('Should sort 21 validator in increasing order', async () => { - // balance: [3M, 3M+1, 3M+2, 3M+3, 3M+4, 3M+5, ...] - for (let i = 0; i < 21; ++i) { - let topupValue = minValidatorBalance.add(ethers.utils.parseEther(i.toString())); - let tx = await stakingContract - .connect(stakingAddrs[i]) - .proposeValidator(consensusAddrs[i].address, treasuryAddrs[i].address, i * 100, { - value: topupValue, - }); - expect(await tx) - .to.emit(stakingContract, 'ValidatorProposed') - .withArgs(consensusAddrs[i].address, stakingAddrs[i].address, topupValue, candidates[i]); - } - - await stakingContract.connect(admin).updateValidatorSet(); - let currentSet = await stakingContract.getCurrentValidatorSet(); - let expectingSet = [DEFAULT_ADDRESS].concat( - Array.from({ length: 21 }, (_, j) => 20 - j).map((i) => consensusAddrs[i].address) - ); - console.log('>>> currentSet', currentSet); - await expect(expectingSet).eql(currentSet); - }); - - it('Should sort 21 validator in mixed order', async () => { - let balances = []; - for (let i = 0; i < 21; ++i) { - balances.push({ - key: i, - value: Math.floor(Math.random() * 1000), - }); - } - - for (let j = 0; j < 21; ++j) { - let i = balances[j].key; - let topupValue = minValidatorBalance.add(ethers.utils.parseEther(balances[j].value.toString())); - let tx = await stakingContract - .connect(stakingAddrs[i]) - .proposeValidator(consensusAddrs[i].address, treasuryAddrs[i].address, i * 100, { - value: topupValue, - }); - expect(await tx) - .to.emit(stakingContract, 'ValidatorProposed') - .withArgs(consensusAddrs[i].address, stakingAddrs[i].address, topupValue, candidates[i]); - } - - balances.sort((a, b) => (a.value < b.value ? 1 : a.value == b.value ? (a.key < b.key ? 1 : -1) : -1)); - console.log( - '>>> balances index after sort', - balances.map((e) => e.key + 1) - ); - - await stakingContract.connect(admin).updateValidatorSet(); - let currentSet = await stakingContract.getCurrentValidatorSet(); - let expectingSet = [DEFAULT_ADDRESS].concat(balances.map((e) => consensusAddrs[e.key].address)); - console.log('>>> currentSet', currentSet); - await expect(expectingSet).eql(currentSet); - }); - - it('Should ignore the second sort for 21 validator', async () => { - let balances = []; - for (let i = 0; i < 21; ++i) { - balances.push({ - key: i, - value: Math.floor(Math.random() * 1000), - }); - } - - for (let j = 0; j < 21; ++j) { - let i = balances[j].key; - let topupValue = minValidatorBalance.add(ethers.utils.parseEther(balances[j].value.toString())); - let tx = await stakingContract - .connect(stakingAddrs[i]) - .proposeValidator(consensusAddrs[i].address, treasuryAddrs[i].address, i * 100, { - value: topupValue, - }); - expect(await tx) - .to.emit(stakingContract, 'ValidatorProposed') - .withArgs(consensusAddrs[i].address, stakingAddrs[i].address, topupValue, candidates[i]); - } - - balances.sort((a, b) => (a.value < b.value ? 1 : a.value == b.value ? (a.key < b.key ? 1 : -1) : -1)); - console.log( - '>>> balances index after sort', - balances.map((e) => e.key + 1) - ); - - // first sort - await stakingContract.connect(admin).updateValidatorSet(); - let currentSet = await stakingContract.getCurrentValidatorSet(); - let expectingSet = [DEFAULT_ADDRESS].concat(balances.map((e) => consensusAddrs[e.key].address)); - await expect(expectingSet).eql(currentSet); - - // second sort - await stakingContract.connect(admin).updateValidatorSet(); - currentSet = await stakingContract.getCurrentValidatorSet(); - expectingSet = [DEFAULT_ADDRESS].concat(balances.map((e) => consensusAddrs[e.key].address)); - await expect(expectingSet).eql(currentSet); - }); - - it('Should ignore the second and third sort for 21 validator', async () => { - let balances = []; - for (let i = 0; i < 21; ++i) { - balances.push({ - key: i, - value: Math.floor(Math.random() * 1000), - }); - } - - for (let j = 0; j < 21; ++j) { - let i = balances[j].key; - let topupValue = minValidatorBalance.add(ethers.utils.parseEther(balances[j].value.toString())); - let tx = await stakingContract - .connect(stakingAddrs[i]) - .proposeValidator(consensusAddrs[i].address, treasuryAddrs[i].address, i * 100, { - value: topupValue, - }); - expect(await tx) - .to.emit(stakingContract, 'ValidatorProposed') - .withArgs(consensusAddrs[i].address, stakingAddrs[i].address, topupValue, candidates[i]); - } - - balances.sort((a, b) => (a.value < b.value ? 1 : a.value == b.value ? (a.key < b.key ? 1 : -1) : -1)); - console.log( - '>>> balances index after sort', - balances.map((e) => e.key + 1) - ); - - // first sort - await stakingContract.connect(admin).updateValidatorSet(); - let currentSet = await stakingContract.getCurrentValidatorSet(); - let expectingSet = [DEFAULT_ADDRESS].concat(balances.map((e) => consensusAddrs[e.key].address)); - await expect(expectingSet).eql(currentSet); - - // second sort - await stakingContract.connect(admin).updateValidatorSet(); - currentSet = await stakingContract.getCurrentValidatorSet(); - expectingSet = [DEFAULT_ADDRESS].concat(balances.map((e) => consensusAddrs[e.key].address)); - await expect(expectingSet).eql(currentSet); - - // third sort - await stakingContract.connect(admin).updateValidatorSet(); - currentSet = await stakingContract.getCurrentValidatorSet(); - expectingSet = [DEFAULT_ADDRESS].concat(balances.map((e) => consensusAddrs[e.key].address)); - await expect(expectingSet).eql(currentSet); - }); - }); - }); -}); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index f476d0b78..098deb966 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -5,15 +5,15 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { DPoStaking, - MockRoninValidatorSet, - MockRoninValidatorSet__factory, + MockRoninValidatorSetEpochSetter, + MockRoninValidatorSetEpochSetter__factory, DPoStaking__factory, TransparentUpgradeableProxy__factory, MockSlashIndicator, MockSlashIndicator__factory, } from '../../src/types'; -let roninValidatorSet: MockRoninValidatorSet; +let roninValidatorSet: MockRoninValidatorSetEpochSetter; let stakingContract: DPoStaking; let slashIndicator: MockSlashIndicator; @@ -46,16 +46,18 @@ describe('Ronin Validator Set test', () => { const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 1 }); const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 3 }); - slashIndicator = await new MockSlashIndicator__factory(deployer).deploy(roninValidatorSetAddr); + slashIndicator = await new MockSlashIndicator__factory(deployer).deploy( + roninValidatorSetAddr, + slashFelonyAmount, + slashDoubleSignAmount + ); await slashIndicator.deployed(); - roninValidatorSet = await new MockRoninValidatorSet__factory(deployer).deploy( + roninValidatorSet = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy( governanceAdmin.address, slashIndicator.address, stakingContractAddr, - maxValidatorNumber, - slashFelonyAmount, - slashDoubleSignAmount + maxValidatorNumber ); await roninValidatorSet.deployed(); diff --git a/test/validator/ValidatorSetCore.test.ts b/test/validator/ValidatorSetCore.test.ts deleted file mode 100644 index 580c36f39..000000000 --- a/test/validator/ValidatorSetCore.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { expect } from 'chai'; -import { ethers } from 'hardhat'; -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; - -import { MockValidatorSetCore, MockValidatorSetCore__factory } from '../../src/types'; -import { ValidatorCandidateStruct } from '../../src/types/IStaking'; - -let validatorsCore: MockValidatorSetCore; - -let signers: SignerWithAddress[]; -let candidates: ValidatorCandidateStruct[]; -let admin: SignerWithAddress; -let stakingAddrs: SignerWithAddress[]; -let consensusAddrs: SignerWithAddress[]; -let treasuryAddrs: SignerWithAddress[]; - -const generateCandidate = ( - candidateAdmin: string, - consensusAddr: string, - treasuryAddr: string -): ValidatorCandidateStruct => { - return { - candidateAdmin: candidateAdmin, - consensusAddr: consensusAddr, - treasuryAddr: treasuryAddr, - commissionRate: 0, - stakedAmount: 0, - delegatedAmount: 0, - governing: false, - state: 0, - ____gap: Array.apply(null, Array(20)).map((_) => 0), - }; -}; - -describe.skip('Validator set core tests', () => { - describe('CRUD functions on validator set', async () => { - describe('Simple CRUD', async () => { - before(async () => { - [admin, ...signers] = await ethers.getSigners(); - validatorsCore = await new MockValidatorSetCore__factory(admin).deploy(); - - candidates = []; - stakingAddrs = []; - consensusAddrs = []; - treasuryAddrs = []; - - for (let i = 0; i < 7; i++) { - candidates.push( - generateCandidate(signers[3 * i].address, signers[3 * i + 1].address, signers[3 * i + 2].address) - ); - stakingAddrs.push(signers[3 * i]); - consensusAddrs.push(signers[3 * i + 1]); - treasuryAddrs.push(signers[3 * i + 2]); - } - }); - - it('Should be able to set one validator (set unexisted validator)', async () => { - await validatorsCore.setValidator(candidates[1], false); - let validator = await validatorsCore.getValidator(consensusAddrs[1].address); - expect(candidates[1].consensusAddr).eq(validator.consensusAddr); - expect(candidates[1].treasuryAddr).eq(validator.treasuryAddr); - }); - - it('Should be able to set one validator (skip to set existed validator, not forced)', async () => { - await validatorsCore.setValidator(candidates[1], false); - let validator = await validatorsCore.getValidator(consensusAddrs[1].address); - expect(candidates[1].consensusAddr).eq(validator.consensusAddr); - expect(candidates[1].treasuryAddr).eq(validator.treasuryAddr); - }); - - it('Should be able to set one validator (set existed validator, forced, to update treasury address)', async () => { - candidates[1].treasuryAddr = treasuryAddrs[5].address; - await validatorsCore.setValidator(candidates[1], true); - let validator = await validatorsCore.getValidator(consensusAddrs[1].address); - expect(candidates[1].consensusAddr).eq(validator.consensusAddr); - expect(treasuryAddrs[5].address).eq(validator.treasuryAddr); - }); - - it('Should fail to query in mining validator set', async () => { - let miningValidator = validatorsCore.getValidatorAtMiningIndex(1); - await expect(miningValidator).to.revertedWith('Validator: No validator exists at queried mining index'); - }); - - it('Should be able set the added validator at 1-index slot (skipping add new validator on actual set)', async () => { - await validatorsCore.setValidatorAtMiningIndex(1, candidates[1]); - let miningValidator = await validatorsCore.getValidatorAtMiningIndex(1); - await expect(miningValidator.consensusAddr).eq(candidates[1].consensusAddr); - }); - - it('Should not be able to retrieve validator at 2-index slot while having 1 validator in the list', async () => { - await expect(validatorsCore.getValidatorAtMiningIndex(2)).to.revertedWith( - 'Validator: No validator exists at queried mining index' - ); - }); - - it('Should be able to add a new validator at 2-index slot while having 1 validator in the list (set new on actual set)', async () => { - let miningValidator = await validatorsCore.getValidatorAtMiningIndex(1); - await expect(miningValidator.consensusAddr).eq(candidates[1].consensusAddr); - await validatorsCore.setValidatorAtMiningIndex(2, candidates[2]); - miningValidator = await validatorsCore.getValidatorAtMiningIndex(2); - await expect(miningValidator.consensusAddr).eq(candidates[2].consensusAddr); - }); - - it('Should not be able to add a new validator at 4-index slot while having 2 validator in the list', async () => { - await expect(validatorsCore.setValidatorAtMiningIndex(4, candidates[3])).to.revertedWith( - 'Validator: Cannot set at out-of-bound mining set' - ); - }); - - it('Should be able to swap the validator', async () => { - await validatorsCore.setValidator(candidates[6], true); - await validatorsCore.setValidatorAtMiningIndex(2, candidates[6]); - let miningValidator = await validatorsCore.getValidatorAtMiningIndex(2); - await expect(miningValidator.consensusAddr).eq(candidates[6].consensusAddr); - await expect(validatorsCore.getValidatorAtMiningIndex(3)).to.revertedWith( - 'Validator: No validator exists at queried mining index' - ); - }); - - it('Should be able to remove the validator', async () => { - expect(await validatorsCore.getCurrentValidatorSetSize()).to.eq(2); - await validatorsCore.popValidatorFromMiningIndex(); - expect(await validatorsCore.getCurrentValidatorSetSize()).to.eq(1); - }); - }); - - describe('Stress test', async () => { - before(async () => { - [admin, ...signers] = await ethers.getSigners(); - candidates = []; - validatorsCore = await new MockValidatorSetCore__factory(admin).deploy(); - - for (let i = 0; i < 33; i++) { - candidates.push( - generateCandidate(signers[3 * i].address, signers[3 * i + 1].address, signers[3 * i + 2].address) - ); - } - }); - - it('Should be able to add 10 validators', async () => { - for (let i = 1; i <= 10; i++) { - await validatorsCore.setValidator(candidates[i], false); - let _validator = await validatorsCore.getValidator(candidates[i].consensusAddr); - expect(_validator.consensusAddr).eq(candidates[i].consensusAddr); - } - }); - - it('Should be able to set in the indexes 10 new validators', async () => { - for (let i = 1; i <= 10; i++) { - await validatorsCore.setValidatorAtMiningIndex(i, candidates[i]); - let _validator = await validatorsCore.getValidatorAtMiningIndex(i); - await expect(_validator.consensusAddr).eq(candidates[i].consensusAddr); - } - }); - - it('Should be able to swap 10 existed validators', async () => { - let swapTable = [...Array(10).keys()].map((x) => ++x).sort(() => 0.5 - Math.random()); - - console.log(swapTable); - - for (let i = 1; i <= 10; i++) { - console.log('>>> Swap index', i, 'to', swapTable[i - 1]); - await validatorsCore.setValidatorAtMiningIndex(i, candidates[swapTable[i - 1]]); - let _validator = await validatorsCore.getValidatorAtMiningIndex(i); - expect(_validator.consensusAddr).eq(candidates[swapTable[i - 1]].consensusAddr); - } - }); - - it('Should be able to add 20 validators more', async () => { - for (let i = 11; i <= 30; i++) { - await validatorsCore.setValidator(candidates[i], false); - let _validator = await validatorsCore.getValidator(candidates[i].consensusAddr); - expect(_validator.consensusAddr).eq(candidates[i].consensusAddr); - } - }); - - it('Should be able to set in the indexes for 11 new validators', async () => { - for (let i = 11; i <= 21; i++) { - await validatorsCore.setValidatorAtMiningIndex(i, candidates[i]); - let _validator = await validatorsCore.getValidatorAtMiningIndex(i); - await expect(_validator.consensusAddr).eq(candidates[i].consensusAddr); - } - }); - - it('Should be able to swap 21 existed validators', async () => { - let swapTable = [...Array(21).keys()].map((x) => ++x).sort(() => 0.5 - Math.random()); - - console.log(swapTable); - - for (let i = 1; i <= 21; i++) { - console.log('>>> Swap index', i, 'to', swapTable[i - 1]); - await validatorsCore.setValidatorAtMiningIndex(i, candidates[swapTable[i - 1]]); - let _validator = await validatorsCore.getValidatorAtMiningIndex(i); - expect(_validator.consensusAddr).eq(candidates[swapTable[i - 1]].consensusAddr); - } - }); - }); - }); -}); From b388710ce53b636e9b71ff5b64e299457465b0e0 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Thu, 8 Sep 2022 16:27:53 +0700 Subject: [PATCH 090/190] [SlashIndicator] Update test to work with new contract --- .../mocks/slash/MockValidatorSetForSlash.sol | 27 +++++ test/slash/SlashIndicator.test.ts | 106 ++++++++++++------ test/slash/slashType.ts | 6 + 3 files changed, 102 insertions(+), 37 deletions(-) create mode 100644 contracts/mocks/slash/MockValidatorSetForSlash.sol create mode 100644 test/slash/slashType.ts diff --git a/contracts/mocks/slash/MockValidatorSetForSlash.sol b/contracts/mocks/slash/MockValidatorSetForSlash.sol new file mode 100644 index 000000000..811378d4e --- /dev/null +++ b/contracts/mocks/slash/MockValidatorSetForSlash.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../../interfaces/ISlashIndicator.sol"; + +contract MockValidatorSetForSlash { + ISlashIndicator private __slashingContract; + + function _setSlashingContract() internal view virtual returns (ISlashIndicator) { + return __slashingContract; + } + + function setSlashingContract(ISlashIndicator _addr) external { + __slashingContract = _addr; + } + + function slash( + address _validatorAddr, + uint256 _newJailedUntil, + uint256 _slashMisdemeanor + ) external {} + + function resetCounters(address[] calldata _addr) external { + __slashingContract.resetCounters(_addr); + } +} diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index 84261f1aa..c7c0261e9 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -5,29 +5,25 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { SlashIndicator, SlashIndicator__factory, + MockValidatorSetForSlash, + MockValidatorSetForSlash__factory, TransparentUpgradeableProxy__factory, - MockValidatorSet__factory, - MockValidatorSet, } from '../../src/types'; import { Address } from 'hardhat-deploy/dist/types'; +import { SlashType } from './slashType'; +import { Network, slashIndicatorConf } from '../../src/config'; +import { BigNumber } from 'ethers'; let slashContract: SlashIndicator; let deployer: SignerWithAddress; let proxyAdmin: SignerWithAddress; -let validatorContract: MockValidatorSet; +let mockValidatorsContract: MockValidatorSetForSlash; let vagabond: SignerWithAddress; let coinbases: SignerWithAddress[]; let defaultCoinbase: Address; let localIndicators: number[]; -enum SlashType { - UNKNOWN, - MISDEMEANOR, - FELONY, - DOUBLE_SIGNING, -} - const resetCoinbase = async () => { await network.provider.send('hardhat_setCoinbase', [defaultCoinbase]); }; @@ -59,30 +55,37 @@ describe('Slash indicator test', () => { before(async () => { [deployer, proxyAdmin, vagabond, ...coinbases] = await ethers.getSigners(); - localIndicators = Array(coinbases.length).fill(0); + defaultCoinbase = await network.provider.send('eth_coinbase'); - const nonce = await deployer.getTransactionCount(); - const slashIndicatorAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); - validatorContract = await new MockValidatorSet__factory(deployer).deploy( - ethers.constants.AddressZero, - slashIndicatorAddr, - 0, - 0 - ); + if (network.name == Network.Hardhat) { + slashIndicatorConf[network.name] = { + misdemeanorThreshold: 10, + felonyThreshold: 20, // set low threshold to get rid of 40000ms of test timeout + slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), // 10 RON + slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), // 10 RON + felonyJailBlocks: 28800 * 2, // jails for 2 days + }; + } + + mockValidatorsContract = await new MockValidatorSetForSlash__factory(deployer).deploy(); const logicContract = await new SlashIndicator__factory(deployer).deploy(); const proxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( logicContract.address, proxyAdmin.address, - logicContract.interface.encodeFunctionData('initialize', [10, 20, validatorContract.address, 0, 0, 28800 * 2]) + logicContract.interface.encodeFunctionData('initialize', [ + slashIndicatorConf[network.name]!.misdemeanorThreshold, + slashIndicatorConf[network.name]!.felonyThreshold, + mockValidatorsContract.address, + slashIndicatorConf[network.name]!.slashFelonyAmount, + slashIndicatorConf[network.name]!.slashDoubleSignAmount, + slashIndicatorConf[network.name]!.felonyJailBlocks, + ]) ); slashContract = SlashIndicator__factory.connect(proxyContract.address, deployer); + await mockValidatorsContract.connect(deployer).setSlashingContract(slashContract.address); - let thresholds = await slashContract.getSlashThresholds(); - felonyThreshold = thresholds[0].toNumber(); - misdemeanorThreshold = thresholds[1].toNumber(); - - defaultCoinbase = await network.provider.send('eth_coinbase'); + [misdemeanorThreshold, felonyThreshold] = (await slashContract.getSlashThresholds()).map((_) => _.toNumber()); }); describe('Single flow test', async () => { @@ -158,33 +161,64 @@ describe('Slash indicator test', () => { }); describe('Slash method: recording and call to validator set', async () => { - it('Should sync with validator set for felony', async () => { + it('Should sync with validator set for misdemeanor (slash tier-1)', async () => { let tx; - let slasherIdx = 0; + let slasherIdx = 1; + let slasheeIdx = 3; + await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); + + for (let i = 0; i < misdemeanorThreshold; i++) { + tx = await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); + } + expect(tx).to.emit(slashContract, 'ValidatorSlashed').withArgs(coinbases[1].address, SlashType.MISDEMEANOR); + await setLocalCounterForValidatorAt(slasheeIdx, misdemeanorThreshold); + await validateIndicatorAt(slasheeIdx); + }); + + it('Should not sync with validator set when the indicator counter is in between misdemeanor (tier-1) and felony (tier-2) thresholds ', async () => { + let tx; + let slasherIdx = 1; let slasheeIdx = 3; + await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); + + tx = await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); + await increaseLocalCounterForValidatorAt(slasheeIdx); + await validateIndicatorAt(slasheeIdx); + + expect(tx).not.to.emit(slashContract, 'ValidatorSlashed'); + }); + + it('Should sync with validator set for felony (slash tier-2)', async () => { + let tx; + let slasherIdx = 0; + let slasheeIdx = 4; await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); for (let i = 0; i < felonyThreshold; i++) { tx = await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); + + if (i == misdemeanorThreshold - 1) { + expect(tx).to.emit(slashContract, 'ValidatorSlashed').withArgs(coinbases[1].address, SlashType.MISDEMEANOR); + } } + expect(tx).to.emit(slashContract, 'ValidatorSlashed').withArgs(coinbases[1].address, SlashType.FELONY); await setLocalCounterForValidatorAt(slasheeIdx, felonyThreshold); await validateIndicatorAt(slasheeIdx); }); - it('Should sync with validator set for misdemeanor', async () => { + it('Should not sync with validator set when the indicator counter exceeds felony threshold (tier-2) ', async () => { let tx; let slasherIdx = 1; let slasheeIdx = 4; await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); - for (let i = 0; i < misdemeanorThreshold; i++) { - tx = await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); - } - expect(tx).to.emit(slashContract, 'ValidatorSlashed').withArgs(coinbases[1].address, SlashType.MISDEMEANOR); - await setLocalCounterForValidatorAt(slasheeIdx, misdemeanorThreshold); + tx = await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); + await increaseLocalCounterForValidatorAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); + + expect(tx).not.to.emit(slashContract, 'ValidatorSlashed'); }); }); @@ -204,7 +238,7 @@ describe('Slash indicator test', () => { await resetCoinbase(); - tx = await validatorContract.resetCounters([coinbases[slasheeIdx].address]); + tx = await mockValidatorsContract.resetCounters([coinbases[slasheeIdx].address]); expect(tx).to.emit(slashContract, 'UnavailabilityIndicatorReset').withArgs(coinbases[slasheeIdx].address); await resetLocalCounterForValidatorAt(slasheeIdx); @@ -231,7 +265,7 @@ describe('Slash indicator test', () => { await resetCoinbase(); - tx = await validatorContract.resetCounters(slasheeIdxs.map((_) => coinbases[_].address)); + tx = await mockValidatorsContract.resetCounters(slasheeIdxs.map((_) => coinbases[_].address)); for (let j = 0; j < slasheeIdxs.length; j++) { expect(tx).to.emit(slashContract, 'UnavailabilityIndicatorReset').withArgs(coinbases[slasheeIdxs[j]].address); @@ -241,6 +275,4 @@ describe('Slash indicator test', () => { }); }); }); - - describe('Integration test', async () => {}); }); diff --git a/test/slash/slashType.ts b/test/slash/slashType.ts new file mode 100644 index 000000000..bed174ea9 --- /dev/null +++ b/test/slash/slashType.ts @@ -0,0 +1,6 @@ +export enum SlashType { + UNKNOWN = 0, + MISDEMEANOR = 1, + FELONY = 2, + DOUBLE_SIGNING = 3, +} From 5a9004e69710017177f63211b4bbef050abf039d Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Thu, 8 Sep 2022 15:43:48 +0700 Subject: [PATCH 091/190] Rename governance admin --- contracts/interfaces/IRoninValidatorSet.sol | 4 +-- contracts/interfaces/ISlashIndicator.sol | 6 ++-- contracts/interfaces/IStaking.sol | 6 ++-- .../MockRoninValidatorSetEpochSetter.sol | 4 +-- contracts/mocks/MockSlashIndicator.sol | 2 +- contracts/mocks/MockValidatorSet.sol | 2 +- .../ronin-validator/RoninValidatorSet.sol | 12 +++---- contracts/slashing/SlashIndicator.sol | 8 ++--- contracts/staking/DPoStaking.sol | 32 +++++++++---------- 9 files changed, 38 insertions(+), 38 deletions(-) diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index 2e83891b8..e9537f5a5 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -44,9 +44,9 @@ interface IRoninValidatorSet { /////////////////////////////////////////////////////////////////////////////////////// /** - * @dev Returns the governance admin contract address. + * @dev Returns the governance admin address. */ - function governanceAdminContract() external view returns (address); + function governanceAdmin() external view returns (address); /** * @dev Returns the slash indicator contract address. diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index b99c81df5..09e77d902 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -58,7 +58,7 @@ interface ISlashIndicator { * @dev Sets the slash thresholds * * Requirements: - * - Only governance admin contract can call this method + * - Only governance admin can call this method * */ function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external; @@ -74,7 +74,7 @@ interface ISlashIndicator { function getSlashThresholds() external view returns (uint256 misdemeanorThreshold, uint256 felonyThreshold); /** - * @dev Returns the governance admin contract address. + * @dev Returns the governance admin address. */ - function governanceAdminContract() external view returns (address); + function governanceAdmin() external view returns (address); } diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 60c33bd01..3355802bf 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -47,7 +47,7 @@ interface IStaking { event Undelegated(address indexed delegator, address indexed validator, uint256 amount); event ValidatorContractUpdated(address); - event GovernanceAdminContractUpdated(address); + event GovernanceAdminUpdated(address); event MinValidatorBalanceUpdated(uint256 threshold); event MaxValidatorCandidateUpdated(uint256 threshold); @@ -56,9 +56,9 @@ interface IStaking { /////////////////////////////////////////////////////////////////////////////////////// /** - * @dev Returns the governance admin contract address. + * @dev Returns the governance admin address. */ - function governanceAdminContract() external view returns (address); + function governanceAdmin() external view returns (address); /** * @dev Returns the minimum threshold for being a validator candidate. diff --git a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol index f757282d3..2a9593fd7 100644 --- a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol +++ b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol @@ -9,12 +9,12 @@ contract MockRoninValidatorSetEpochSetter is RoninValidatorSet { uint256[] internal _periods; constructor( - address __governanceAdminContract, + address __governanceAdmin, address __slashIndicatorContract, address __stakingContract, uint256 __maxValidatorNumber ) { - _governanceAdminContract = __governanceAdminContract; + _governanceAdmin = __governanceAdmin; _slashIndicatorContract = __slashIndicatorContract; _stakingContract = __stakingContract; _maxValidatorNumber = __maxValidatorNumber; diff --git a/contracts/mocks/MockSlashIndicator.sol b/contracts/mocks/MockSlashIndicator.sol index c9e5fb290..c1410992d 100644 --- a/contracts/mocks/MockSlashIndicator.sol +++ b/contracts/mocks/MockSlashIndicator.sol @@ -50,5 +50,5 @@ contract MockSlashIndicator is ISlashIndicator { function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external override {} - function governanceAdminContract() external view override returns (address) {} + function governanceAdmin() external view override returns (address) {} } diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index 2c040fedf..f4e623ece 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -67,7 +67,7 @@ contract MockValidatorSet is IRoninValidatorSet { function getLastUpdatedBlock() external view override returns (uint256) {} - function governanceAdminContract() external view override returns (address) {} + function governanceAdmin() external view override returns (address) {} function jailed(address[] memory) external view override returns (bool[] memory) {} diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index ccabda9c5..b197efe89 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -11,8 +11,8 @@ import "../libraries/Sorting.sol"; import "../libraries/Math.sol"; contract RoninValidatorSet is IRoninValidatorSet, Initializable { - /// @dev Governance admin contract address. - address internal _governanceAdminContract; // TODO(Thor): add setter. + /// @dev Governance admin address. + address internal _governanceAdmin; // TODO(Thor): add setter. /// @dev Slash indicator contract address. address internal _slashIndicatorContract; // Change type to address for testing purpose /// @dev Staking contract address. @@ -69,14 +69,14 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { * @dev Initializes the contract storage. */ function initialize( - address __governanceAdminContract, + address __governanceAdmin, address __slashIndicatorContract, address __stakingContract, uint256 __maxValidatorNumber, uint256 __numberOfBlocksInEpoch, uint256 __numberOfEpochsInPeriod ) external initializer { - _governanceAdminContract = __governanceAdminContract; + _governanceAdmin = __governanceAdmin; _slashIndicatorContract = __slashIndicatorContract; _stakingContract = __stakingContract; _maxValidatorNumber = __maxValidatorNumber; @@ -181,8 +181,8 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { /** * @inheritdoc IRoninValidatorSet */ - function governanceAdminContract() external view override returns (address) { - return _governanceAdminContract; + function governanceAdmin() external view override returns (address) { + return _governanceAdmin; } /** diff --git a/contracts/slashing/SlashIndicator.sol b/contracts/slashing/SlashIndicator.sol index 627a47040..f28c92576 100644 --- a/contracts/slashing/SlashIndicator.sol +++ b/contracts/slashing/SlashIndicator.sol @@ -26,8 +26,8 @@ contract SlashIndicator is ISlashIndicator, Initializable { /// @dev The validator contract IRoninValidatorSet public validatorContract; - /// @dev The governance admin contract - address internal _governanceAdminContract; + /// @dev The governance admin + address internal _governanceAdmin; modifier onlyCoinbase() { require(msg.sender == block.coinbase, "SlashIndicator: method caller is not the coinbase"); @@ -162,7 +162,7 @@ contract SlashIndicator is ISlashIndicator, Initializable { /** * @inheritdoc ISlashIndicator */ - function governanceAdminContract() external view override returns (address) { - return _governanceAdminContract; + function governanceAdmin() external view override returns (address) { + return _governanceAdmin; } } diff --git a/contracts/staking/DPoStaking.sol b/contracts/staking/DPoStaking.sol index feb5566b6..df5657f76 100644 --- a/contracts/staking/DPoStaking.sol +++ b/contracts/staking/DPoStaking.sol @@ -14,8 +14,8 @@ contract DPoStaking is IStaking, StakingManager, Initializable { uint256 internal _minValidatorBalance; /// @dev Maximum number of validator. uint256 internal _maxValidatorCandidate; - /// @dev Governance admin contract address. - address internal _governanceAdminContract; // TODO(Thor): add setter. + /// @dev Governance admin address. + address internal _governanceAdmin; // TODO(Thor): add setter. /// @dev Validator contract address. address internal _validatorContract; // Change type to address for testing purpose @@ -26,8 +26,8 @@ contract DPoStaking is IStaking, StakingManager, Initializable { /// @dev Mapping from consensus address => period index => indicating the pending reward in the period is sinked or not. mapping(address => mapping(uint256 => bool)) internal _pRewardSinked; - modifier onlyGovernanceAdminContract() { - require(msg.sender == _governanceAdminContract, "DPoStaking: method caller is not governance admin contract"); + modifier onlyGovernanceAdmin() { + require(msg.sender == _governanceAdmin, "DPoStaking: method caller is not governance admin"); _; } @@ -49,12 +49,12 @@ contract DPoStaking is IStaking, StakingManager, Initializable { */ function initialize( address __validatorContract, - address __governanceAdminContract, + address __governanceAdmin, uint256 __maxValidatorCandidate, uint256 __minValidatorBalance ) external initializer { _setValidatorContract(__validatorContract); - _setGovernanceAdminContractAddress(__governanceAdminContract); + _setGovernanceAdminAddress(__governanceAdmin); _setMaxValidatorCandidate(__maxValidatorCandidate); _setMinValidatorBalance(__minValidatorBalance); } @@ -66,8 +66,8 @@ contract DPoStaking is IStaking, StakingManager, Initializable { /** * @inheritdoc IStaking */ - function governanceAdminContract() public view override returns (address) { - return _governanceAdminContract; + function governanceAdmin() public view override returns (address) { + return _governanceAdmin; } /** @@ -80,7 +80,7 @@ contract DPoStaking is IStaking, StakingManager, Initializable { /** * @inheritdoc IStaking */ - function setMinValidatorBalance(uint256 _threshold) external onlyGovernanceAdminContract { + function setMinValidatorBalance(uint256 _threshold) external onlyGovernanceAdmin { _setMinValidatorBalance(_threshold); } @@ -94,7 +94,7 @@ contract DPoStaking is IStaking, StakingManager, Initializable { /** * @inheritdoc IStaking */ - function setMaxValidatorCandidate(uint256 _threshold) external onlyGovernanceAdminContract { + function setMaxValidatorCandidate(uint256 _threshold) external onlyGovernanceAdmin { _setMaxValidatorCandidate(_threshold); } @@ -214,18 +214,18 @@ contract DPoStaking is IStaking, StakingManager, Initializable { } /** - * @dev Sets the governance admin contract address. + * @dev Sets the governance admin address. * - * Emits the `GovernanceAdminContractUpdated` event. + * Emits the `GovernanceAdminUpdated` event. * */ - function _setGovernanceAdminContractAddress(address _newAddr) internal { - _governanceAdminContract = _newAddr; - emit GovernanceAdminContractUpdated(_newAddr); + function _setGovernanceAdminAddress(address _newAddr) internal { + _governanceAdmin = _newAddr; + emit GovernanceAdminUpdated(_newAddr); } /** - * @dev Sets the governance admin contract address. + * @dev Sets the governance admin address. * * Emits the `ValidatorContractUpdated` event. * From 4617a75b5eef3fade4d3cda9eaffa8a969f053d7 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Thu, 8 Sep 2022 16:17:20 +0700 Subject: [PATCH 092/190] Rename DPoStaking to Staking --- contracts/staking/{DPoStaking.sol => Staking.sol} | 8 ++++---- src/config.ts | 4 ++-- src/deploy/calculate-address.ts | 2 +- src/deploy/logic/dpostaking.ts | 6 +++--- src/deploy/proxy/dpostaking-proxy.ts | 12 ++++++------ src/deploy/verify-address.ts | 4 ++-- test/staking/DPoStaking.test.ts | 10 +++++----- test/validator/RoninValidatorSet.test.ts | 10 +++++----- 8 files changed, 28 insertions(+), 28 deletions(-) rename contracts/staking/{DPoStaking.sol => Staking.sol} (96%) diff --git a/contracts/staking/DPoStaking.sol b/contracts/staking/Staking.sol similarity index 96% rename from contracts/staking/DPoStaking.sol rename to contracts/staking/Staking.sol index df5657f76..1600a23b3 100644 --- a/contracts/staking/DPoStaking.sol +++ b/contracts/staking/Staking.sol @@ -9,7 +9,7 @@ import "../interfaces/IRoninValidatorSet.sol"; import "../libraries/Sorting.sol"; import "./StakingManager.sol"; -contract DPoStaking is IStaking, StakingManager, Initializable { +contract Staking is IStaking, StakingManager, Initializable { /// @dev The minimum threshold for being a validator candidate. uint256 internal _minValidatorBalance; /// @dev Maximum number of validator. @@ -27,12 +27,12 @@ contract DPoStaking is IStaking, StakingManager, Initializable { mapping(address => mapping(uint256 => bool)) internal _pRewardSinked; modifier onlyGovernanceAdmin() { - require(msg.sender == _governanceAdmin, "DPoStaking: method caller is not governance admin"); + require(msg.sender == _governanceAdmin, "Staking: method caller is not governance admin"); _; } modifier onlyValidatorContract() { - require(msg.sender == _validatorContract, "DPoStaking: method caller is not the validator contract"); + require(msg.sender == _validatorContract, "Staking: method caller is not the validator contract"); _; } @@ -188,7 +188,7 @@ contract DPoStaking is IStaking, StakingManager, Initializable { returns (ValidatorCandidate storage _candidate) { uint256 _idx = _candidateIndex[_consensusAddr]; - require(_idx > 0, "DPoStaking: query for nonexistent candidate"); + require(_idx > 0, "Staking: query for nonexistent candidate"); _candidate = validatorCandidates[~_idx]; } diff --git a/src/config.ts b/src/config.ts index 98c376841..8764b8d7e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -21,7 +21,7 @@ export interface InitAddr { }; } -export interface DPoStakingConf { +export interface StakingConf { [network: LiteralNetwork]: | { maxValidatorCandidate: BigNumberish; @@ -59,7 +59,7 @@ export const initAddress: InitAddr = { }; // TODO: update config for devnet, testnet & mainnet -export const stakingConfig: DPoStakingConf = { +export const stakingConfig: StakingConf = { [Network.Hardhat]: undefined, [Network.Devnet]: { minValidatorBalance: BigNumber.from(10).pow(18).mul(1000), // 1000 RON diff --git a/src/deploy/calculate-address.ts b/src/deploy/calculate-address.ts index 6a9da77f5..8b2dbdaa4 100644 --- a/src/deploy/calculate-address.ts +++ b/src/deploy/calculate-address.ts @@ -11,6 +11,6 @@ const deploy = async ({ getNamedAccounts }: HardhatRuntimeEnvironment) => { }; deploy.tags = ['CalculateAddresses']; -deploy.dependencies = ['ProxyAdmin', 'DPoStakingLogic', 'SlashIndicatorLogic', 'RoninValidatorSetLogic']; +deploy.dependencies = ['ProxyAdmin', 'StakingLogic', 'SlashIndicatorLogic', 'RoninValidatorSetLogic']; export default deploy; diff --git a/src/deploy/logic/dpostaking.ts b/src/deploy/logic/dpostaking.ts index 945bc9efa..bf15d0b0f 100644 --- a/src/deploy/logic/dpostaking.ts +++ b/src/deploy/logic/dpostaking.ts @@ -4,14 +4,14 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const { deploy } = deployments; const { deployer } = await getNamedAccounts(); - await deploy('DPoStakingLogic', { - contract: 'DPoStaking', + await deploy('StakingLogic', { + contract: 'Staking', from: deployer, log: true, }); }; -deploy.tags = ['DPoStakingLogic']; +deploy.tags = ['StakingLogic']; deploy.dependencies = ['ProxyAdmin']; export default deploy; diff --git a/src/deploy/proxy/dpostaking-proxy.ts b/src/deploy/proxy/dpostaking-proxy.ts index 59bfb4a20..6dc85ea53 100644 --- a/src/deploy/proxy/dpostaking-proxy.ts +++ b/src/deploy/proxy/dpostaking-proxy.ts @@ -1,23 +1,23 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { stakingConfig, initAddress } from '../../config'; -import { DPoStaking__factory } from '../../types'; +import { Staking__factory } from '../../types'; const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { const { deploy } = deployments; const { deployer } = await getNamedAccounts(); const proxyAdmin = await deployments.get('ProxyAdmin'); - const logicContract = await deployments.get('DPoStakingLogic'); + const logicContract = await deployments.get('StakingLogic'); - const data = new DPoStaking__factory().interface.encodeFunctionData('initialize', [ + const data = new Staking__factory().interface.encodeFunctionData('initialize', [ initAddress[network.name]!.validatorContract, initAddress[network.name]!.governanceAdmin, stakingConfig[network.name]!.maxValidatorCandidate, stakingConfig[network.name]!.minValidatorBalance, ]); - await deploy('DPoStakingProxy', { + await deploy('StakingProxy', { contract: 'TransparentUpgradeableProxy', from: deployer, log: true, @@ -25,7 +25,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme }); }; -deploy.tags = ['DPoStakingProxy']; -deploy.dependencies = ['ProxyAdmin', 'DPoStakingLogic', 'SlashIndicatorProxy']; +deploy.tags = ['StakingProxy']; +deploy.dependencies = ['ProxyAdmin', 'StakingLogic', 'SlashIndicatorProxy']; export default deploy; diff --git a/src/deploy/verify-address.ts b/src/deploy/verify-address.ts index 3bceec12a..cf352d9ba 100644 --- a/src/deploy/verify-address.ts +++ b/src/deploy/verify-address.ts @@ -4,7 +4,7 @@ import { initAddress } from '../config'; const deploy = async ({ deployments }: HardhatRuntimeEnvironment) => { const indicator = await deployments.get('SlashIndicatorProxy'); - const stakingContract = await deployments.get('DPoStakingProxy'); + const stakingContract = await deployments.get('StakingProxy'); const validatorContract = await deployments.get('RoninValidatorSetProxy'); if (initAddress[network.name].slashIndicator?.toLowerCase() != indicator.address.toLowerCase()) { @@ -32,6 +32,6 @@ const deploy = async ({ deployments }: HardhatRuntimeEnvironment) => { }; deploy.tags = ['VerifyAddress']; -deploy.dependencies = ['ProxyAdmin', 'DPoStakingProxy', 'SlashIndicatorProxy', 'RoninValidatorSetProxy']; +deploy.dependencies = ['ProxyAdmin', 'StakingProxy', 'SlashIndicatorProxy', 'RoninValidatorSetProxy']; export default deploy; diff --git a/test/staking/DPoStaking.test.ts b/test/staking/DPoStaking.test.ts index 9f507040d..c46a3d893 100644 --- a/test/staking/DPoStaking.test.ts +++ b/test/staking/DPoStaking.test.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { BigNumber, BigNumberish } from 'ethers'; import { ethers, network } from 'hardhat'; -import { DPoStaking, DPoStaking__factory, TransparentUpgradeableProxy__factory } from '../../src/types'; +import { Staking, Staking__factory, TransparentUpgradeableProxy__factory } from '../../src/types'; import { MockValidatorSet__factory } from '../../src/types/factories/MockValidatorSet__factory'; import { MockValidatorSet } from '../../src/types/MockValidatorSet'; @@ -17,7 +17,7 @@ let userA: SignerWithAddress; let userB: SignerWithAddress; let governanceAdmin: SignerWithAddress; let validatorContract: MockValidatorSet; -let stakingContract: DPoStaking; +let stakingContract: Staking; let validatorCandidates: SignerWithAddress[]; const local = { @@ -89,7 +89,7 @@ const expectLocalCalculationRight = async () => { const minValidatorBalance = BigNumber.from(2); -describe('DPoStaking test', () => { +describe('Staking test', () => { before(async () => { [deployer, proxyAdmin, userA, userB, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); validatorCandidates = validatorCandidates.slice(0, 3); @@ -102,7 +102,7 @@ describe('DPoStaking test', () => { 2 ); await validatorContract.deployed(); - const logicContract = await new DPoStaking__factory(deployer).deploy(); + const logicContract = await new Staking__factory(deployer).deploy(); await logicContract.deployed(); const proxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( logicContract.address, @@ -115,7 +115,7 @@ describe('DPoStaking test', () => { ]) ); await proxyContract.deployed(); - stakingContract = DPoStaking__factory.connect(proxyContract.address, deployer); + stakingContract = Staking__factory.connect(proxyContract.address, deployer); expect(stakingContractAddr.toLowerCase()).eq(stakingContract.address.toLowerCase()); }); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index 098deb966..3058cd37e 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -4,17 +4,17 @@ import { ethers, network } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { - DPoStaking, + Staking, MockRoninValidatorSetEpochSetter, MockRoninValidatorSetEpochSetter__factory, - DPoStaking__factory, + Staking__factory, TransparentUpgradeableProxy__factory, MockSlashIndicator, MockSlashIndicator__factory, } from '../../src/types'; let roninValidatorSet: MockRoninValidatorSetEpochSetter; -let stakingContract: DPoStaking; +let stakingContract: Staking; let slashIndicator: MockSlashIndicator; let coinbase: SignerWithAddress; @@ -61,7 +61,7 @@ describe('Ronin Validator Set test', () => { ); await roninValidatorSet.deployed(); - const logicContract = await new DPoStaking__factory(deployer).deploy(); + const logicContract = await new Staking__factory(deployer).deploy(); await logicContract.deployed(); const proxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( @@ -75,7 +75,7 @@ describe('Ronin Validator Set test', () => { ]) ); await proxyContract.deployed(); - stakingContract = DPoStaking__factory.connect(proxyContract.address, deployer); + stakingContract = Staking__factory.connect(proxyContract.address, deployer); expect(roninValidatorSetAddr.toLowerCase()).eq(roninValidatorSet.address.toLowerCase()); expect(stakingContractAddr.toLowerCase()).eq(stakingContract.address.toLowerCase()); From a500d0a6b8bc53fa72f8725b696dd4cb05eeb305 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Thu, 8 Sep 2022 17:43:17 +0700 Subject: [PATCH 093/190] [RoninValidatorSet] add events & update tests --- contracts/interfaces/IRoninValidatorSet.sol | 21 +++- .../ronin-validator/RoninValidatorSet.sol | 22 +++- src/script/dpostaking.ts | 2 - src/script/ronin-validator-set.ts | 103 ++++++++++++++++++ src/utils.ts | 27 ++++- test/validator/RoninValidatorSet.test.ts | 71 ++++++++---- 6 files changed, 217 insertions(+), 29 deletions(-) delete mode 100644 src/script/dpostaking.ts create mode 100644 src/script/ronin-validator-set.ts diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index e9537f5a5..8685d4807 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -5,12 +5,18 @@ pragma solidity ^0.8.9; import "./ISlashIndicator.sol"; interface IRoninValidatorSet { - /// @dev Emitted when the reward of the valdiator is deprecated + /// @dev Emitted when the reward of the valdiator is deprecated. event RewardDeprecated(address coinbaseAddr, uint256 rewardAmount); - /// @dev Emitted when the block reward is submitted + /// @dev Emitted when the block reward is submitted. event BlockRewardSubmitted(address coinbaseAddr, uint256 rewardAmount); /// @dev Emitted when the validator is slashed. event ValidatorSlashed(address validatorAddr, uint256 jailedUntil, uint256 deductedStakingAmount); + /// @dev Emitted when the validator reward is distributed. + event MiningRewardDistributed(address validatorAddr, uint256 amount); + /// @dev Emitted when the amount of RON reward is distributed. + event StakingRewardDistributed(uint256 amount); + /// @dev Emitted when the validator set is updated + event ValidatorSetUpdated(address[]); /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR COINBASE // @@ -22,6 +28,9 @@ interface IRoninValidatorSet { * Requirements: * - The method caller is coinbase. * + * Emits the event `RewardDeprecated` if the coinbase is slashed or no longer be a validator. + * Emits the event `BlockRewardSubmitted` for the valid call. + * */ function submitBlockReward() external payable; @@ -29,8 +38,14 @@ interface IRoninValidatorSet { * @dev Wraps up the current epoch. * * Requirements: + * - The method must be called when the current epoch is ending. + * - The epoch is not wrapped yet. * - The method caller is coinbase. * + * Emits the event `MiningRewardDistributed` when some validator has reward distributed. + * Emits the event `StakingRewardDistributed` when some staking pool has reward distributed. + * Emits the event `ValidatorSetUpdated`. + * */ function wrapUpEpoch() external payable; @@ -64,6 +79,8 @@ interface IRoninValidatorSet { * Requirements: * - The method caller is slash indicator contract. * + * Emits the event `ValidatorSlashed`. + * */ function slash( address _validatorAddr, diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index b197efe89..e7ccdd3da 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -36,7 +36,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { /// @dev Mapping from epoch index => flag indicating the epoch is wrapped up or not mapping(uint256 => bool) internal _wrappedUp; - /// @dev Mapping from validator address => the last **period** that the validator has no pending reward + /// @dev Mapping from validator address => the last period that the validator has no pending reward mapping(address => mapping(uint256 => bool)) internal _rewardDeprecatedAtPeriod; /// @dev Mapping from validator address => the last block that the validator is jailed mapping(address => uint256) internal _jailedUntil; @@ -106,7 +106,6 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { return; } - emit BlockRewardSubmitted(_coinbaseAddr, _reward); IStaking _staking = IStaking(_stakingContract); uint256 _rate = _staking.commissionRateOf(_coinbaseAddr); uint256 _miningAmount = (_rate * _reward) / 100_00; @@ -115,6 +114,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _miningReward[_coinbaseAddr] += _miningAmount; _delegatingReward[_coinbaseAddr] += _delegatingAmount; _staking.recordReward(_coinbaseAddr, _delegatingAmount); + emit BlockRewardSubmitted(_coinbaseAddr, _reward); } /** @@ -146,12 +146,12 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { address _treasury = _staking.treasuryAddressOf(_validatorAddr); (bool _success, ) = _treasury.call{ value: _miningAmount }(""); require(_success, "RoninValidatorSet: could not transfer RON treasury addr"); + emit MiningRewardDistributed(_validatorAddr, _miningAmount); } } _delegatingAmount += _delegatingReward[_validatorAddr]; delete _delegatingReward[_validatorAddr]; - // TODO: emit events } if (_periodEnding) { @@ -162,6 +162,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { if (_delegatingAmount > 0) { (bool _success, ) = address(_staking).call{ value: _delegatingAmount }(""); require(_success, "RoninValidatorSet: could not transfer RON to staking contract"); + emit StakingRewardDistributed(_delegatingAmount); } _updateValidatorSet(); @@ -303,6 +304,10 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { return _numberOfBlocksInEpoch; } + /////////////////////////////////////////////////////////////////////////////////////// + // HELPER FUNCTIONS // + /////////////////////////////////////////////////////////////////////////////////////// + /** * @dev Returns whether the reward of the validator is put in jail (cannot join the set of validators) during the current period. */ @@ -326,8 +331,12 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { /** * @dev Updates the validator set based on the validator candidates from the Staking contract. + * + * Emits the `ValidatorSetUpdated` event. + * */ function _updateValidatorSet() internal { + // TODO: measure gas metrics from getting candidates (address[] memory _candidates, uint256[] memory _weights) = IStaking(_stakingContract).getCandidateWeights(); uint256 _newLength = _candidates.length; for (uint256 _i; _i < _candidates.length; _i++) { @@ -344,8 +353,13 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { } _candidates = Sorting.sort(_candidates, _weights); + // TODO: measure gas metrics to after sorting candidates uint256 _newValidatorCount = Math.min(_maxValidatorNumber, _candidates.length); + assembly { + mstore(_candidates, _newValidatorCount) + } + // TODO: pick at least M governers as validators for (uint256 _i = _newValidatorCount; _i < validatorCount; _i++) { @@ -363,6 +377,6 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { validatorCount = _newValidatorCount; _lastUpdatedBlock = block.number; - // TODO(Thor): emit validator set updated. + emit ValidatorSetUpdated(_candidates); } } diff --git a/src/script/dpostaking.ts b/src/script/dpostaking.ts deleted file mode 100644 index 4a6026238..000000000 --- a/src/script/dpostaking.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { BigNumberish } from 'ethers'; -import { LiteralNetwork, Network } from '../config'; diff --git a/src/script/ronin-validator-set.ts b/src/script/ronin-validator-set.ts new file mode 100644 index 000000000..accc97fb3 --- /dev/null +++ b/src/script/ronin-validator-set.ts @@ -0,0 +1,103 @@ +import { expect } from 'chai'; +import { BigNumberish, ContractTransaction } from 'ethers'; + +import { expectEvent } from '../utils'; +import { RoninValidatorSet__factory } from '../types'; + +const contractInterface = RoninValidatorSet__factory.createInterface(); + +export const expects = { + emitRewardDeprecatedEvent: async function ( + tx: ContractTransaction, + expectedCoinbaseAddr: string, + expectedDeprecatedReward: BigNumberish + ) { + expectEvent( + contractInterface, + 'RewardDeprecated', + tx, + (event) => { + expect(event.args[0], 'invalid coinbase address').eq(expectedCoinbaseAddr); + expect(event.args[1], 'invalid reward').eq(expectedDeprecatedReward); + }, + 1 + ); + }, + + emitBlockRewardSubmittedEvent: async function ( + tx: ContractTransaction, + expectedCoinbaseAddr: string, + expectedDeprecatedReward: BigNumberish + ) { + expectEvent( + contractInterface, + 'BlockRewardSubmitted', + tx, + (event) => { + expect(event.args[0], 'invalid coinbase address').eq(expectedCoinbaseAddr); + expect(event.args[1], 'invalid reward').eq(expectedDeprecatedReward); + }, + 1 + ); + }, + + emitValidatorSlashedEvent: async function ( + tx: ContractTransaction, + expectedValidatorAddr: string, + expectedJailedUntil: BigNumberish, + expectedDeductedStakingAmount: BigNumberish + ) { + expectEvent( + contractInterface, + 'ValidatorSlashed', + tx, + (event) => { + expect(event.args[0], 'invalid coinbase address').eq(expectedValidatorAddr); + expect(event.args[1], 'invalid jailed until').eq(expectedJailedUntil); + expect(event.args[2], 'invalid deducted staking amount').eq(expectedDeductedStakingAmount); + }, + 1 + ); + }, + + emitMiningRewardDistributedEvent: async function ( + tx: ContractTransaction, + expectedCoinbaseAddr: string, + expectedAmount: BigNumberish + ) { + expectEvent( + contractInterface, + 'MiningRewardDistributed', + tx, + (event) => { + expect(event.args[0], 'invalid coinbase address').eq(expectedCoinbaseAddr); + expect(event.args[1], 'invalid amount').eq(expectedAmount); + }, + 1 + ); + }, + + emitStakingRewardDistributedEvent: async function (tx: ContractTransaction, expectedAmount: BigNumberish) { + expectEvent( + contractInterface, + 'StakingRewardDistributed', + tx, + (event) => { + expect(event.args[0], 'invalid deprecated reward').eq(expectedAmount); + }, + 1 + ); + }, + + emitValidatorSetUpdatedEvent: async function (tx: ContractTransaction, expectedValidators: string[]) { + expectEvent( + contractInterface, + 'ValidatorSetUpdated', + tx, + (event) => { + expect(event.args[0], 'invalid validator set').have.deep.members(expectedValidators); + }, + 1 + ); + }, +}; diff --git a/src/utils.ts b/src/utils.ts index e733e8453..19d83cd16 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,6 @@ -import { BigNumber } from 'ethers'; +import { expect } from 'chai'; +import { BigNumber, ContractTransaction } from 'ethers'; +import { Interface, LogDescription } from 'ethers/lib/utils'; export const DEFAULT_ADDRESS = '0x0000000000000000000000000000000000000000'; export const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000'; @@ -9,3 +11,26 @@ export const randomBigNumber = () => { .join(''); return BigNumber.from(`0x${hexString}`); }; + +export const expectEvent = async ( + contractInterface: Interface, + eventName: string, + tx: ContractTransaction, + expectFn: (log: LogDescription) => void, + eventNumbers?: number +) => { + const receipt = await tx.wait(); + const topic = contractInterface.getEventTopic(eventName); + let counter = 0; + + for (let i = 0; i < receipt.logs.length; i++) { + const eventLog = receipt.logs[i]; + if (eventLog.topics[0] == topic) { + counter++; + const event = contractInterface.parseLog(eventLog); + expectFn(event); + } + } + + expect(counter, 'invalid number of emitted events').eq(eventNumbers); +}; diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index 3058cd37e..db663fd61 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { BigNumber } from 'ethers'; +import { BigNumber, ContractTransaction } from 'ethers'; import { ethers, network } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; @@ -12,6 +12,7 @@ import { MockSlashIndicator, MockSlashIndicator__factory, } from '../../src/types'; +import * as RoninValidatorSet from '../../src/script/ronin-validator-set'; let roninValidatorSet: MockRoninValidatorSetEpochSetter; let stakingContract: Staking; @@ -24,6 +25,8 @@ let governanceAdmin: SignerWithAddress; let proxyAdmin: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; +let currentValidatorSet: string[]; + const slashFelonyAmount = 100; const slashDoubleSignAmount = 1000; const maxValidatorNumber = 4; @@ -98,10 +101,12 @@ describe('Ronin Validator Set test', () => { }); it('Should be able to wrap up epoch when the epoch is ending', async () => { + let tx: ContractTransaction; await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); - await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, []); expect(await roninValidatorSet.getValidators()).have.same.members([]); }); @@ -114,10 +119,18 @@ describe('Ronin Validator Set test', () => { }); } + let tx: ContractTransaction; await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); - await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent( + tx!, + validatorCandidates + .slice(0, 4) + .reverse() + .map((_) => _.address) + ); expect(await roninValidatorSet.getValidators()).have.same.members( validatorCandidates @@ -140,18 +153,20 @@ describe('Ronin Validator Set test', () => { } expect((await stakingContract.getValidatorCandidates()).length).eq(validatorCandidates.length + 1); + let tx: ContractTransaction; await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); - await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + currentValidatorSet = [ + coinbase.address, + ...validatorCandidates + .slice(2) + .reverse() + .map((_) => _.address), + ]; }); - - expect(await roninValidatorSet.getValidators()).have.same.members([ - coinbase.address, - ...validatorCandidates - .slice(2) - .reverse() - .map((_) => _.address), - ]); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); + expect(await roninValidatorSet.getValidators()).have.same.members(currentValidatorSet); }); it('Should not be able to submit block reward using unauthorized account', async () => { @@ -161,34 +176,43 @@ describe('Ronin Validator Set test', () => { }); it('Should be able to submit block reward using coinbase account', async () => { - await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + const tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + await RoninValidatorSet.expects.emitBlockRewardSubmittedEvent(tx, coinbase.address, 100); }); it('Should be able to get right reward at the end of period', async () => { const balance = await treasury.getBalance(); + let tx: ContractTransaction; await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); await roninValidatorSet.endPeriod(); - await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); + await RoninValidatorSet.expects.emitStakingRewardDistributedEvent(tx!, 99); + await RoninValidatorSet.expects.emitMiningRewardDistributedEvent(tx!, coinbase.address, 1); const balanceDiff = (await treasury.getBalance()).sub(balance); expect(balanceDiff).eq(1); // 100 * 1% expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(99); // remain amount (99%) }); it('Should not allocate minting fee for the slashed validators', async () => { + let tx: ContractTransaction; { const balance = await treasury.getBalance(); await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); - await slashIndicator.slashMisdemeanor(coinbase.address); + tx = await slashIndicator.slashMisdemeanor(coinbase.address); + await RoninValidatorSet.expects.emitValidatorSlashedEvent(tx!, coinbase.address, 0, 0); + await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); - await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); const balanceDiff = (await treasury.getBalance()).sub(balance); expect(balanceDiff).eq(0); // The delegators don't receives the new rewards until the period is ended expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(99); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); } { @@ -197,37 +221,44 @@ describe('Ronin Validator Set test', () => { await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); await roninValidatorSet.endPeriod(); - await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); const balanceDiff = (await treasury.getBalance()).sub(balance); expect(balanceDiff).eq(0); expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(99); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); } }); it('Should be able to record delegating reward for a successful epoch', async () => { + let tx: ContractTransaction; const balance = await treasury.getBalance(); await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); - await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); const balanceDiff = (await treasury.getBalance()).sub(balance); expect(balanceDiff).eq(0); expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(99 * 2); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); + await RoninValidatorSet.expects.emitStakingRewardDistributedEvent(tx!, 99); }); it('Should not allocate reward for the slashed validator', async () => { + let tx: ContractTransaction; const balance = await treasury.getBalance(); - await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + await RoninValidatorSet.expects.emitRewardDeprecatedEvent(tx!, coinbase.address, 100); await slashIndicator.slashMisdemeanor(coinbase.address); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); await roninValidatorSet.endPeriod(); - await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); const balanceDiff = (await treasury.getBalance()).sub(balance); expect(balanceDiff).eq(0); expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(99 * 2); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); }); }); From 80a0d182ccba21722b4760f23d82cb608079d11a Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Thu, 8 Sep 2022 18:31:28 +0700 Subject: [PATCH 094/190] Separate Staking interface, update comments for events --- contracts/interfaces/IRewardPool.sol | 82 ++++++++++++++++++++++++ contracts/interfaces/ISlashIndicator.sol | 4 +- contracts/interfaces/IStaking.sol | 17 ++++- contracts/staking/RewardCalculation.sol | 66 +++---------------- contracts/staking/StakingManager.sol | 19 ++++-- src/config.ts | 6 +- 6 files changed, 124 insertions(+), 70 deletions(-) create mode 100644 contracts/interfaces/IRewardPool.sol diff --git a/contracts/interfaces/IRewardPool.sol b/contracts/interfaces/IRewardPool.sol new file mode 100644 index 000000000..7bcf35628 --- /dev/null +++ b/contracts/interfaces/IRewardPool.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IRewardPool { + /// @dev Emitted when the settled pool is updated. + event SettledPoolsUpdated(address[] poolAddress, uint256[] accumulatedRps); + /// @dev Emitted when the pending pool is updated. + event PendingPoolUpdated(address poolAddress, uint256 accumulatedRps); + /// @dev Emitted when the fields to calculate settled reward for the user is updated. + event SettledRewardUpdated( + address poolAddress, + address user, + uint256 balance, + uint256 debited, + uint256 accumulatedRps + ); + /// @dev Emitted when the fields to calculate pending reward for the user is updated. + event PendingRewardUpdated(address poolAddress, address user, uint256 debited, uint256 credited); + /// @dev Emitted when the user claimed their reward + event RewardClaimed(address poolAddress, address user, uint256 amount); + + struct PendingRewardFields { + // Recorded reward amount. + uint256 debited; + // The amount rewards that user have already earned. + uint256 credited; + // Last block number that the info updated. + uint256 lastSyncBlock; + } + + struct SettledRewardFields { + // The balance at the commit time. + uint256 balance; + // Recorded reward amount. + uint256 debited; + // Accumulated of the amount rewards per share (one unit staking). + uint256 accumulatedRps; + } + + struct PendingPool { + // Last block number that the info updated. + uint256 lastSyncBlock; + // Accumulated of the amount rewards per share (one unit staking). + uint256 accumulatedRps; + } + + struct SettledPool { + // Last block number that the info updated. + uint256 lastSyncBlock; + // Accumulated of the amount rewards per share (one unit staking). + uint256 accumulatedRps; + } + + /** + * @dev Returns total rewards from scratch including pending reward and claimable reward except the claimed amount. + * + * @notice Do not use this function to get claimable reward, consider using the method `getClaimableReward` instead. + * + */ + function getTotalReward(address _poolAddr, address _user) external view returns (uint256); + + /** + * @dev Returns the reward amount that user claimable. + */ + function getClaimableReward(address _poolAddr, address _user) external view returns (uint256); + + /** + * @dev Returns the pending reward. + */ + function getPendingReward(address _poolAddr, address _user) external view returns (uint256 _amount); + + /** + * @dev Returns the staking amount of the user. + */ + function balanceOf(address _poolAddr, address _user) external view returns (uint256); + + /** + * @dev Returns the total staking amount of all users. + */ + function totalBalance(address _poolAddr) external view returns (uint256); +} diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index 09e77d902..b7626d006 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -5,9 +5,9 @@ pragma solidity ^0.8.9; import "../interfaces/IRoninValidatorSet.sol"; interface ISlashIndicator { - // TODO: fill comment for event. IE: Emitted when... - // TODO: add event for thresholds + /// @dev Emitted when the validator is slashed event ValidatorSlashed(address indexed validator, SlashType slashType); + /// @dev Emitted when the validator indicators are reset event UnavailabilityIndicatorsReset(address[] validators); enum SlashType { diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 3355802bf..081a0bc5e 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -2,7 +2,9 @@ pragma solidity ^0.8.9; -interface IStaking { +import "./IRewardPool.sol"; + +interface IStaking is IRewardPool { enum ValidatorState { ACTIVE, ON_REQUESTING_RENOUNCE, @@ -32,23 +34,32 @@ interface IStaking { uint256[20] ____gap; } - /// @dev TODO: add comment for these events + /// @dev Emitted when the validator candidate is proposed. event ValidatorProposed( address indexed consensusAddr, address indexed candidateIdx, uint256 amount, ValidatorCandidate _info ); + /// @dev Emitted when the candidate admin staked for themself. event Staked(address indexed validator, uint256 amount); + /// @dev Emitted when the candidate admin unstaked the amount of RON from themself. event Unstaked(address indexed validator, uint256 amount); + /// @dev Emitted when the validator candidate requested to renounce. event ValidatorRenounceRequested(address indexed consensusAddr, uint256 amount); + /// @dev Emitted when the renounce request is finalized. event ValidatorRenounceFinalized(address indexed consensusAddr, uint256 amount); + /// @dev Emitted when the delegator staked for a validator. event Delegated(address indexed delegator, address indexed validator, uint256 amount); + /// @dev Emitted when the delegator unstaked from a validator. event Undelegated(address indexed delegator, address indexed validator, uint256 amount); - + /// @dev Emitted when the address of validator contract is updated. event ValidatorContractUpdated(address); + /// @dev Emitted when the address of governance admin is updated. event GovernanceAdminUpdated(address); + /// @dev Emitted when the minimum balance for being a validator is updated. event MinValidatorBalanceUpdated(uint256 threshold); + /// @dev Emitted when the maximum number of validator candidates is updated. event MaxValidatorCandidateUpdated(uint256 threshold); /////////////////////////////////////////////////////////////////////////////////////// diff --git a/contracts/staking/RewardCalculation.sol b/contracts/staking/RewardCalculation.sol index 45b855c4d..31ef0a1f6 100644 --- a/contracts/staking/RewardCalculation.sol +++ b/contracts/staking/RewardCalculation.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.9; +import "../interfaces/IRewardPool.sol"; + /** * @title RewardCalculation contract * @dev This contract mainly contains to calculate reward for staking contract. @@ -9,56 +11,7 @@ pragma solidity ^0.8.9; * TODO(Thor): optimize gas cost when emitting SettledRewardUpdated and PendingRewardUpdated in the method `_claimReward`; * */ -abstract contract RewardCalculation { - /// @dev Emitted when the settled pool is updated. - event SettledPoolsUpdated(address[] poolAddress, uint256[] accumulatedRps); - /// @dev Emitted when the pending pool is updated. - event PendingPoolUpdated(address poolAddress, uint256 accumulatedRps); - /// @dev Emitted when the fields to calculate settled reward for the user is updated. - event SettledRewardUpdated( - address poolAddress, - address user, - uint256 balance, - uint256 debited, - uint256 accumulatedRps - ); - /// @dev Emitted when the fields to calculate pending reward for the user is updated. - event PendingRewardUpdated(address poolAddress, address user, uint256 debited, uint256 credited); - /// @dev Emitted when the user claimed their reward - event RewardClaimed(address poolAddress, address user, uint256 amount); - - struct PendingRewardFields { - // Recorded reward amount. - uint256 debited; - // The amount rewards that user have already earned. - uint256 credited; - // Last block number that the info updated. - uint256 lastSyncBlock; - } - - struct SettledRewardFields { - // The balance at the commit time. - uint256 balance; - // Recorded reward amount. - uint256 debited; - // Accumulated of the amount rewards per share (one unit staking). - uint256 accumulatedRps; - } - - struct PendingPool { - // Last block number that the info updated. - uint256 lastSyncBlock; - // Accumulated of the amount rewards per share (one unit staking). - uint256 accumulatedRps; - } - - struct SettledPool { - // Last block number that the info updated. - uint256 lastSyncBlock; - // Accumulated of the amount rewards per share (one unit staking). - uint256 accumulatedRps; - } - +abstract contract RewardCalculation is IRewardPool { /// @dev Mapping from the pool address => user address => settled reward info of the user mapping(address => mapping(address => SettledRewardFields)) internal _sUserReward; /// @dev Mapping from the pool address => user address => pending reward info of the user @@ -70,10 +23,7 @@ abstract contract RewardCalculation { mapping(address => SettledPool) internal _settledPool; /** - * @dev Returns total rewards from scratch including pending reward and claimable reward except the claimed amount. - * - * @notice Do not use this function to get claimable reward, consider using the method `getClaimableReward` instead. - * + * @inheritdoc IRewardPool */ function getTotalReward(address _poolAddr, address _user) public view returns (uint256) { PendingRewardFields memory _reward = _pUserReward[_poolAddr][_user]; @@ -90,7 +40,7 @@ abstract contract RewardCalculation { } /** - * @dev Returns the reward amount that user claimable. + * @inheritdoc IRewardPool */ function getClaimableReward(address _poolAddr, address _user) public view returns (uint256) { PendingRewardFields memory _reward = _pUserReward[_poolAddr][_user]; @@ -110,19 +60,19 @@ abstract contract RewardCalculation { } /** - * @dev Returns the pending reward. + * @inheritdoc IRewardPool */ function getPendingReward(address _poolAddr, address _user) external view returns (uint256 _amount) { _amount = getTotalReward(_poolAddr, _user) - getClaimableReward(_poolAddr, _user); } /** - * @dev Returns the staking amount of the user. + * @inheritdoc IRewardPool */ function balanceOf(address _poolAddr, address _user) public view virtual returns (uint256); /** - * @dev Returns the total staking amount of all users. + * @inheritdoc IRewardPool */ function totalBalance(address _poolAddr) public view virtual returns (uint256); diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 1d59fc24e..e8e471f9a 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -21,22 +21,33 @@ abstract contract StakingManager is IStaking, RewardCalculation { } /** - * @inheritdoc RewardCalculation + * @inheritdoc IRewardPool */ - function balanceOf(address _poolAddr, address _user) public view override returns (uint256) { + function balanceOf(address _poolAddr, address _user) + public + view + override(IRewardPool, RewardCalculation) + returns (uint256) + { return _delegatedAmount[_poolAddr][_user]; } /** - * @inheritdoc RewardCalculation + * @inheritdoc IRewardPool */ - function totalBalance(address _poolAddr) public view override returns (uint256) { + function totalBalance(address _poolAddr) public view override(IRewardPool, RewardCalculation) returns (uint256) { ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); return _candidate.delegatedAmount; } + /** + * @inheritdoc IStaking + */ function minValidatorBalance() public view virtual returns (uint256); + /** + * @inheritdoc IStaking + */ function maxValidatorCandidate() public view virtual returns (uint256); /////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/config.ts b/src/config.ts index 8764b8d7e..fcfb38c7f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -58,7 +58,7 @@ export const initAddress: InitAddr = { }, }; -// TODO: update config for devnet, testnet & mainnet +// TODO: update config for testnet & mainnet export const stakingConfig: StakingConf = { [Network.Hardhat]: undefined, [Network.Devnet]: { @@ -69,7 +69,7 @@ export const stakingConfig: StakingConf = { [Network.Mainnet]: undefined, }; -// TODO: update config for devnet, testnet & mainnet +// TODO: update config for testnet & mainnet export const slashIndicatorConf: SlashIndicatorConf = { [Network.Hardhat]: undefined, [Network.Devnet]: { @@ -83,7 +83,7 @@ export const slashIndicatorConf: SlashIndicatorConf = { [Network.Mainnet]: undefined, }; -// TODO: update config for devnet, testnet & mainnet +// TODO: update config for testnet & mainnet export const roninValidatorSetConf: RoninValidatorSetConf = { [Network.Hardhat]: undefined, [Network.Devnet]: { From 4a8fedca5655331dce8415c3240ae3cf9f0a484a Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Fri, 9 Sep 2022 11:31:38 +0700 Subject: [PATCH 095/190] Update devnet config (#2) --- src/config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.ts b/src/config.ts index fcfb38c7f..a60703473 100644 --- a/src/config.ts +++ b/src/config.ts @@ -62,7 +62,7 @@ export const initAddress: InitAddr = { export const stakingConfig: StakingConf = { [Network.Hardhat]: undefined, [Network.Devnet]: { - minValidatorBalance: BigNumber.from(10).pow(18).mul(1000), // 1000 RON + minValidatorBalance: BigNumber.from(10).pow(18).mul(BigNumber.from(10).pow(5)), // 100.000 RON maxValidatorCandidate: 100, }, [Network.Testnet]: undefined, @@ -75,7 +75,7 @@ export const slashIndicatorConf: SlashIndicatorConf = { [Network.Devnet]: { misdemeanorThreshold: 50, felonyThreshold: 150, - slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), // 10 RON + slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), // 1 RON slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), // 10 RON felonyJailBlocks: 28800 * 2, // jails for 2 days }, From bdd4f6e7f0d6df049dd53ed76731abf87499cfac Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Fri, 9 Sep 2022 11:41:45 +0700 Subject: [PATCH 096/190] Update README.md (#3) * Update devnet config * Update links --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7f1ed2927..daefc4061 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ The delegator can choose the validator to stake and receive the commission rewar ### Reward Calculation -- Read how the reward is calculated for delegator at [Staking Problem: Reward Calculation](https://www.notion.so/skymavis/Staking-Problem-Reward-Calculation-bd47bbcefde24bbd8e959bee45dfd4a5). +- Read how the reward is calculated for delegator at [Staking Problem: Reward Calculation](https://skymavis.notion.site/Staking-Problem-Reward-Calculation-bd47bbcefde24bbd8e959bee45dfd4a5). - See [`RewardCalculation` contract](./contracts/staking/RewardCalculation.sol) for the implementation. ## Validator Contract @@ -96,7 +96,7 @@ The validators will be slashed when they do not provide the good service for Ron ## Contract Interaction flow -Read the contract interaction flow at [DPoS Contract: Interaction Flow](https://www.notion.so/skymavis/DPoS-Contract-Interaction-Flow-3a535cf9048f46f69dd9a45958ad9b85). +Read the contract interaction flow at [DPoS Contract: Interaction Flow](https://skymavis.notion.site/DPoS-Contract-Interaction-Flow-3a535cf9048f46f69dd9a45958ad9b85). ## Development @@ -120,7 +120,7 @@ $ yarn test $ cp .env.example .env && vim .env ``` -- Update the contract configuration in [`config.ts`](./src/config.ts#L54-L93) file +- Update the contract configuration in [`config.ts`](./src/config.ts#L55-L96) file - Deploy the contracts: From f4d58568dd288cb169edbb1333f56ee925944e41 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 13 Sep 2022 14:45:27 +0700 Subject: [PATCH 097/190] [Staking] Rename variable & remove unnecessary variable (#4) ### Description - Change variable name `lastSyncBlock ` -> `lastSyncedBlock ` - Remove the last sync block for pending reward pool ### Checklist - [x] The box that allows repo maintainers to update this PR is checked - [x] I tested locally to make sure this feature/fix works --- contracts/interfaces/IRewardPool.sol | 6 ++---- contracts/staking/RewardCalculation.sol | 14 ++++++-------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/contracts/interfaces/IRewardPool.sol b/contracts/interfaces/IRewardPool.sol index 7bcf35628..224af5a02 100644 --- a/contracts/interfaces/IRewardPool.sol +++ b/contracts/interfaces/IRewardPool.sol @@ -26,7 +26,7 @@ interface IRewardPool { // The amount rewards that user have already earned. uint256 credited; // Last block number that the info updated. - uint256 lastSyncBlock; + uint256 lastSyncedBlock; } struct SettledRewardFields { @@ -39,15 +39,13 @@ interface IRewardPool { } struct PendingPool { - // Last block number that the info updated. - uint256 lastSyncBlock; // Accumulated of the amount rewards per share (one unit staking). uint256 accumulatedRps; } struct SettledPool { // Last block number that the info updated. - uint256 lastSyncBlock; + uint256 lastSyncedBlock; // Accumulated of the amount rewards per share (one unit staking). uint256 accumulatedRps; } diff --git a/contracts/staking/RewardCalculation.sol b/contracts/staking/RewardCalculation.sol index 31ef0a1f6..974ff9df7 100644 --- a/contracts/staking/RewardCalculation.sol +++ b/contracts/staking/RewardCalculation.sol @@ -30,7 +30,7 @@ abstract contract RewardCalculation is IRewardPool { PendingPool memory _pool = _pendingPool[_poolAddr]; uint256 _balance = balanceOf(_poolAddr, _user); - if (_rewardSinked(_poolAddr, _periodOf(_reward.lastSyncBlock))) { + if (_rewardSinked(_poolAddr, _periodOf(_reward.lastSyncedBlock))) { SettledRewardFields memory _sReward = _sUserReward[_poolAddr][_user]; uint256 _credited = (_sReward.accumulatedRps * _balance) / 1e18; return (_balance * _pool.accumulatedRps) / 1e18 + _sReward.debited - _credited; @@ -47,7 +47,7 @@ abstract contract RewardCalculation is IRewardPool { SettledRewardFields memory _sReward = _sUserReward[_poolAddr][_user]; SettledPool memory _sPool = _settledPool[_poolAddr]; - if (_reward.lastSyncBlock <= _sPool.lastSyncBlock) { + if (_reward.lastSyncedBlock <= _sPool.lastSyncedBlock) { uint256 _currentBalance = balanceOf(_poolAddr, _user); _sReward.debited = (_currentBalance * _sPool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; _sReward.balance = _currentBalance; @@ -94,7 +94,7 @@ abstract contract RewardCalculation is IRewardPool { SettledPool memory _sPool = _settledPool[_poolAddr]; // Syncs the reward once the last sync is settled. - if (_reward.lastSyncBlock <= _sPool.lastSyncBlock) { + if (_reward.lastSyncedBlock <= _sPool.lastSyncedBlock) { uint256 _claimableReward = getClaimableReward(_poolAddr, _user); uint256 _balance = balanceOf(_poolAddr, _user); @@ -111,7 +111,7 @@ abstract contract RewardCalculation is IRewardPool { _reward.debited = _debited; _reward.credited = _credited; - _reward.lastSyncBlock = block.number; + _reward.lastSyncedBlock = block.number; emit PendingRewardUpdated(_poolAddr, _user, _debited, _credited); } @@ -130,7 +130,7 @@ abstract contract RewardCalculation is IRewardPool { PendingRewardFields storage _reward = _pUserReward[_poolAddr][_user]; _reward.credited += _amount; - _reward.lastSyncBlock = block.number; + _reward.lastSyncedBlock = block.number; emit PendingRewardUpdated(_poolAddr, _user, _reward.debited, _reward.credited); SettledRewardFields storage _sReward = _sUserReward[_poolAddr][_user]; @@ -151,7 +151,6 @@ abstract contract RewardCalculation is IRewardPool { PendingPool storage _pool = _pendingPool[_poolAddr]; uint256 _accumulatedRps = _pool.accumulatedRps + (_reward * 1e18) / totalBalance(_poolAddr); _pool.accumulatedRps = _accumulatedRps; - _pool.lastSyncBlock = block.number; emit PendingPoolUpdated(_poolAddr, _accumulatedRps); } @@ -167,7 +166,6 @@ abstract contract RewardCalculation is IRewardPool { uint256 _accumulatedRps = _settledPool[_poolAddr].accumulatedRps; PendingPool storage _pool = _pendingPool[_poolAddr]; _pool.accumulatedRps = _accumulatedRps; - _pool.lastSyncBlock = block.number; emit PendingPoolUpdated(_poolAddr, _accumulatedRps); } @@ -189,7 +187,7 @@ abstract contract RewardCalculation is IRewardPool { SettledPool storage _sPool = _settledPool[_poolAddr]; _sPool.accumulatedRps = _accumulatedRps; - _sPool.lastSyncBlock = block.number; + _sPool.lastSyncedBlock = block.number; } emit SettledPoolsUpdated(_poolList, _accumulatedRpsList); } From d2fcd65c2e0ac53a172b6fe3dbcb320c8ff38ef9 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 13 Sep 2022 15:10:43 +0700 Subject: [PATCH 098/190] [Staking/Validator] Add bonus per block (#7) ### Description - Add block reward per block in `StakingVesting` contract - Request block bonus while submitting block reward - `RoninValidatorSet.submitBlockReward()` - Update the contract deployment order ### Config changes (devnet) - Top up `10.000` RON to `StakingVesting` contract in the initializing transaction. - Add `1` RON as the bonus reward for each block --- contracts/StakingVesting.sol | 89 +++++++++++++++++++ contracts/interfaces/IRoninValidatorSet.sol | 5 ++ contracts/interfaces/IStakingVesting.sol | 40 +++++++++ .../MockRoninValidatorSetEpochSetter.sol | 2 + contracts/mocks/MockValidatorSet.sol | 3 + .../ronin-validator/RoninValidatorSet.sol | 29 ++++++ src/config.ts | 21 +++++ src/deploy/calculate-address.ts | 12 ++- src/deploy/logic/staking-vesting.ts | 17 ++++ .../logic/{dpostaking.ts => staking.ts} | 0 src/deploy/proxy/ronin-validator-proxy.ts | 3 +- .../{dpostaking-proxy.ts => staking-proxy.ts} | 0 src/deploy/proxy/staking-vesting-proxy.ts | 30 +++++++ src/deploy/verify-address.ts | 16 +++- .../{DPoStaking.test.ts => Staking.test.ts} | 3 + test/validator/RoninValidatorSet.test.ts | 13 ++- 16 files changed, 278 insertions(+), 5 deletions(-) create mode 100644 contracts/StakingVesting.sol create mode 100644 contracts/interfaces/IStakingVesting.sol create mode 100644 src/deploy/logic/staking-vesting.ts rename src/deploy/logic/{dpostaking.ts => staking.ts} (100%) rename src/deploy/proxy/{dpostaking-proxy.ts => staking-proxy.ts} (100%) create mode 100644 src/deploy/proxy/staking-vesting-proxy.ts rename test/staking/{DPoStaking.test.ts => Staking.test.ts} (98%) diff --git a/contracts/StakingVesting.sol b/contracts/StakingVesting.sol new file mode 100644 index 000000000..16afef5ce --- /dev/null +++ b/contracts/StakingVesting.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "./interfaces/IStakingVesting.sol"; + +contract StakingVesting is Initializable, IStakingVesting { + /// @dev The block bonus whenever a new block is mined. + uint256 internal _bonusPerBlock; + /// @dev The last block number that the bonus reward sent. + uint256 public lastBonusSentBlock; + /// @dev Validator contract address. + address internal _validatorContract; + + modifier onlyValidatorContract() { + require(msg.sender == _validatorContract, "Staking: method caller is not the validator contract"); + _; + } + + constructor() { + _disableInitializers(); + } + + receive() external payable onlyValidatorContract {} + + fallback() external payable onlyValidatorContract {} + + /** + * @dev Initializes the contract storage. + */ + function initialize(uint256 __bonusPerBlock, address __validatorContract) external payable initializer { + _setBonusPerBlock(__bonusPerBlock); + _setValidatorContract(__validatorContract); + } + + /** + * @inheritdoc IStakingVesting + */ + function receiveRON() external payable {} + + /** + * @inheritdoc IStakingVesting + */ + function blockBonus( + uint256 /* _block */ + ) public view returns (uint256) { + return _bonusPerBlock; + } + + /** + * @inheritdoc IStakingVesting + */ + function requestBlockBonus() external onlyValidatorContract returns (uint256 _amount) { + uint256 _block = block.number; + + require(_block > lastBonusSentBlock, "Staking: bonus already sent"); + lastBonusSentBlock = _block; + _amount = blockBonus(_block); + + if (_amount > 0) { + (bool _success, ) = payable(_validatorContract).call{ value: _amount }(""); + require(_success, "Staking: could not transfer RON to validator contract"); + emit BlockBonusTransferred(_block, _validatorContract, _amount); + } + } + + /** + * @dev Sets the bonus amount per block. + * + * Emits the event `BonusPerBlockUpdated`. + * + */ + function _setBonusPerBlock(uint256 _amount) internal { + _bonusPerBlock = _amount; + emit BonusPerBlockUpdated(_amount); + } + + /** + * @dev Sets the governance admin address. + * + * Emits the `ValidatorContractUpdated` event. + * + */ + function _setValidatorContract(address _newValidatorContract) internal { + _validatorContract = _newValidatorContract; + emit ValidatorContractUpdated(_newValidatorContract); + } +} diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index 8685d4807..5b7fea33f 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -73,6 +73,11 @@ interface IRoninValidatorSet { */ function stakingContract() external view returns (address); + /** + * @dev Returns the staking vesting contract address. + */ + function stakingVestingContract() external view returns (address); + /** * @dev Slashes the validator. * diff --git a/contracts/interfaces/IStakingVesting.sol b/contracts/interfaces/IStakingVesting.sol new file mode 100644 index 000000000..55157719a --- /dev/null +++ b/contracts/interfaces/IStakingVesting.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IStakingVesting { + /// @dev Emitted when the block bonus is transferred. + event BlockBonusTransferred(uint256 indexed blockNumber, address indexed recipient, uint256 amount); + /// @dev Emitted when the block bonus is updated + event BonusPerBlockUpdated(uint256); + /// @dev Emitted when the address of validator contract is updated. + event ValidatorContractUpdated(address); + + /** + * @dev Returns the bonus amount for the block `_block`. + */ + function blockBonus(uint256 _block) external view returns (uint256); + + /** + * @dev Receives RON from any address. + */ + function receiveRON() external payable; + + /** + * @dev Returns the last block number that the bonus reward is sent. + */ + function lastBonusSentBlock() external view returns (uint256); + + /** + * @dev Transfers the bonus reward whenever a new block is mined. + * Returns the amount of RON sent to validator contract. + * + * Requirements: + * - The method caller is validator contract. + * - The method must be called only once per block. + * + * Emits the event `BlockBonusTransferred`. + * + */ + function requestBlockBonus() external returns (uint256); +} diff --git a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol index 2a9593fd7..5eb7e4cc3 100644 --- a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol +++ b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol @@ -12,11 +12,13 @@ contract MockRoninValidatorSetEpochSetter is RoninValidatorSet { address __governanceAdmin, address __slashIndicatorContract, address __stakingContract, + address __stakingVestingContract, uint256 __maxValidatorNumber ) { _governanceAdmin = __governanceAdmin; _slashIndicatorContract = __slashIndicatorContract; _stakingContract = __stakingContract; + _stakingVestingContract = __stakingVestingContract; _maxValidatorNumber = __maxValidatorNumber; } diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index f4e623ece..a89193fce 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -8,6 +8,7 @@ import "../interfaces/IStaking.sol"; contract MockValidatorSet is IRoninValidatorSet { address public stakingContract; + address public stakingVestingContract; address public slashIndicatorContract; uint256 public numberOfEpochsInPeriod; @@ -19,11 +20,13 @@ contract MockValidatorSet is IRoninValidatorSet { constructor( address _stakingContract, address _slashIndicatorContract, + address _stakingVestingContract, uint256 _numberOfEpochsInPeriod, uint256 _numberOfBlocksInEpoch ) { stakingContract = _stakingContract; slashIndicatorContract = _slashIndicatorContract; + stakingVestingContract = _stakingVestingContract; numberOfEpochsInPeriod = _numberOfEpochsInPeriod; numberOfBlocksInEpoch = _numberOfBlocksInEpoch; } diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index e7ccdd3da..eb4cd48d2 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -6,6 +6,7 @@ import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "../interfaces/ISlashIndicator.sol"; import "../interfaces/IStaking.sol"; +import "../interfaces/IStakingVesting.sol"; import "../interfaces/IRoninValidatorSet.sol"; import "../libraries/Sorting.sol"; import "../libraries/Math.sol"; @@ -17,6 +18,8 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { address internal _slashIndicatorContract; // Change type to address for testing purpose /// @dev Staking contract address. address internal _stakingContract; // Change type to address for testing purpose + /// @dev Staking vesting contract address. + address internal _stakingVestingContract; /// @dev The total of validators uint256 public validatorCount; @@ -65,6 +68,14 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _disableInitializers(); } + fallback() external payable { + _fallback(); + } + + receive() external payable { + _fallback(); + } + /** * @dev Initializes the contract storage. */ @@ -72,6 +83,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { address __governanceAdmin, address __slashIndicatorContract, address __stakingContract, + address __stakingVestingContract, uint256 __maxValidatorNumber, uint256 __numberOfBlocksInEpoch, uint256 __numberOfEpochsInPeriod @@ -79,6 +91,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _governanceAdmin = __governanceAdmin; _slashIndicatorContract = __slashIndicatorContract; _stakingContract = __stakingContract; + _stakingVestingContract = __stakingVestingContract; _maxValidatorNumber = __maxValidatorNumber; _numberOfBlocksInEpoch = __numberOfBlocksInEpoch; _numberOfEpochsInPeriod = __numberOfEpochsInPeriod; @@ -106,6 +119,8 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { return; } + _reward += IStakingVesting(_stakingVestingContract).requestBlockBonus(); + IStaking _staking = IStaking(_stakingContract); uint256 _rate = _staking.commissionRateOf(_coinbaseAddr); uint256 _miningAmount = (_rate * _reward) / 100_00; @@ -200,6 +215,13 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { return _stakingContract; } + /** + * @inheritdoc IRoninValidatorSet + */ + function stakingVestingContract() external view override returns (address) { + return _stakingVestingContract; + } + /** * @inheritdoc IRoninValidatorSet */ @@ -379,4 +401,11 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _lastUpdatedBlock = block.number; emit ValidatorSetUpdated(_candidates); } + + /** + * @dev Only receives RON from staking vesting contract. + */ + function _fallback() internal view { + require(msg.sender == _stakingVestingContract, "RoninValidatorSet: method caller must be staking vesting contract"); + } } diff --git a/src/config.ts b/src/config.ts index a60703473..be0a6b40d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -17,6 +17,7 @@ export interface InitAddr { governanceAdmin: string; validatorContract?: string; stakingContract?: string; + stakingVestingContract?: string; slashIndicator?: string; }; } @@ -30,6 +31,15 @@ export interface StakingConf { | undefined; } +export interface StakingVestingConf { + [network: LiteralNetwork]: + | { + bonusPerBlock: BigNumber; + topupAmount: BigNumber; + } + | undefined; +} + export interface SlashIndicatorConf { [network: LiteralNetwork]: | { @@ -69,6 +79,17 @@ export const stakingConfig: StakingConf = { [Network.Mainnet]: undefined, }; +// TODO: update config for testnet & mainnet +export const stakingVestingConfig: StakingVestingConf = { + [Network.Hardhat]: undefined, + [Network.Devnet]: { + bonusPerBlock: BigNumber.from(10).pow(18), // 1 RON per block + topupAmount: BigNumber.from(10).pow(18).mul(BigNumber.from(10).pow(4)), // 10.000 RON + }, + [Network.Testnet]: undefined, + [Network.Mainnet]: undefined, +}; + // TODO: update config for testnet & mainnet export const slashIndicatorConf: SlashIndicatorConf = { [Network.Hardhat]: undefined, diff --git a/src/deploy/calculate-address.ts b/src/deploy/calculate-address.ts index 8b2dbdaa4..292f8bb3d 100644 --- a/src/deploy/calculate-address.ts +++ b/src/deploy/calculate-address.ts @@ -7,10 +7,20 @@ const deploy = async ({ getNamedAccounts }: HardhatRuntimeEnvironment) => { let nonce = await ethers.provider.getTransactionCount(deployer); initAddress[network.name].slashIndicator = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++ }); initAddress[network.name].stakingContract = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++ }); + initAddress[network.name].stakingVestingContract = ethers.utils.getContractAddress({ + from: deployer, + nonce: nonce++, + }); initAddress[network.name].validatorContract = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++ }); }; deploy.tags = ['CalculateAddresses']; -deploy.dependencies = ['ProxyAdmin', 'StakingLogic', 'SlashIndicatorLogic', 'RoninValidatorSetLogic']; +deploy.dependencies = [ + 'ProxyAdmin', + 'StakingLogic', + 'SlashIndicatorLogic', + 'RoninValidatorSetLogic', + 'StakingVestingLogic', +]; export default deploy; diff --git a/src/deploy/logic/staking-vesting.ts b/src/deploy/logic/staking-vesting.ts new file mode 100644 index 000000000..68198bd0d --- /dev/null +++ b/src/deploy/logic/staking-vesting.ts @@ -0,0 +1,17 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + await deploy('StakingVestingLogic', { + contract: 'StakingVesting', + from: deployer, + log: true, + }); +}; + +deploy.tags = ['StakingVestingLogic']; +deploy.dependencies = ['ProxyAdmin']; + +export default deploy; diff --git a/src/deploy/logic/dpostaking.ts b/src/deploy/logic/staking.ts similarity index 100% rename from src/deploy/logic/dpostaking.ts rename to src/deploy/logic/staking.ts diff --git a/src/deploy/proxy/ronin-validator-proxy.ts b/src/deploy/proxy/ronin-validator-proxy.ts index 83ff95f45..c877a38e4 100644 --- a/src/deploy/proxy/ronin-validator-proxy.ts +++ b/src/deploy/proxy/ronin-validator-proxy.ts @@ -14,6 +14,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme initAddress[network.name]!.governanceAdmin, initAddress[network.name]!.slashIndicator, initAddress[network.name]!.stakingContract, + initAddress[network.name]!.stakingVestingContract, roninValidatorSetConf[network.name]!.maxValidatorNumber, roninValidatorSetConf[network.name]!.numberOfBlocksInEpoch, roninValidatorSetConf[network.name]!.numberOfEpochsInPeriod, @@ -28,6 +29,6 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme }; deploy.tags = ['RoninValidatorSetProxy']; -deploy.dependencies = ['ProxyAdmin', 'RoninValidatorSetLogic']; +deploy.dependencies = ['ProxyAdmin', 'RoninValidatorSetLogic', 'SlashIndicatorProxy', 'StakingProxy', 'StakingVestingProxy']; export default deploy; diff --git a/src/deploy/proxy/dpostaking-proxy.ts b/src/deploy/proxy/staking-proxy.ts similarity index 100% rename from src/deploy/proxy/dpostaking-proxy.ts rename to src/deploy/proxy/staking-proxy.ts diff --git a/src/deploy/proxy/staking-vesting-proxy.ts b/src/deploy/proxy/staking-vesting-proxy.ts new file mode 100644 index 000000000..3c3f99e88 --- /dev/null +++ b/src/deploy/proxy/staking-vesting-proxy.ts @@ -0,0 +1,30 @@ +import { network } from 'hardhat'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { initAddress, stakingVestingConfig } from '../../config'; +import { StakingVesting__factory } from '../../types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + const proxyAdmin = await deployments.get('ProxyAdmin'); + const logicContract = await deployments.get('StakingVestingLogic'); + + const data = new StakingVesting__factory().interface.encodeFunctionData('initialize', [ + stakingVestingConfig[network.name]!.bonusPerBlock, + initAddress[network.name]!.validatorContract, + ]); + + await deploy('StakingVestingProxy', { + contract: 'TransparentUpgradeableProxy', + from: deployer, + log: true, + args: [logicContract.address, proxyAdmin.address, data], + value: stakingVestingConfig[network.name]!.topupAmount, + }); +}; + +deploy.tags = ['StakingVestingProxy']; +deploy.dependencies = ['ProxyAdmin', 'RoninValidatorSetLogic', 'SlashIndicatorProxy', 'StakingProxy']; + +export default deploy; diff --git a/src/deploy/verify-address.ts b/src/deploy/verify-address.ts index cf352d9ba..bdea941c4 100644 --- a/src/deploy/verify-address.ts +++ b/src/deploy/verify-address.ts @@ -5,6 +5,7 @@ import { initAddress } from '../config'; const deploy = async ({ deployments }: HardhatRuntimeEnvironment) => { const indicator = await deployments.get('SlashIndicatorProxy'); const stakingContract = await deployments.get('StakingProxy'); + const stakingVestingContract = await deployments.get('StakingVestingProxy'); const validatorContract = await deployments.get('RoninValidatorSetProxy'); if (initAddress[network.name].slashIndicator?.toLowerCase() != indicator.address.toLowerCase()) { @@ -21,6 +22,13 @@ const deploy = async ({ deployments }: HardhatRuntimeEnvironment) => { ].stakingContract?.toLowerCase()}, actual=${stakingContract.address.toLowerCase()}` ); } + if (initAddress[network.name].stakingVestingContract?.toLowerCase() != stakingVestingContract.address.toLowerCase()) { + throw Error( + `invalid address for stakingVestingContract, expected=${initAddress[ + network.name + ].stakingVestingContract?.toLowerCase()}, actual=${stakingVestingContract.address.toLowerCase()}` + ); + } if (initAddress[network.name].validatorContract?.toLowerCase() != validatorContract.address.toLowerCase()) { throw Error( `invalid address for validatorContract, expected=${initAddress[ @@ -32,6 +40,12 @@ const deploy = async ({ deployments }: HardhatRuntimeEnvironment) => { }; deploy.tags = ['VerifyAddress']; -deploy.dependencies = ['ProxyAdmin', 'StakingProxy', 'SlashIndicatorProxy', 'RoninValidatorSetProxy']; +deploy.dependencies = [ + 'ProxyAdmin', + 'StakingProxy', + 'SlashIndicatorProxy', + 'StakingVestingProxy', + 'RoninValidatorSetProxy', +]; export default deploy; diff --git a/test/staking/DPoStaking.test.ts b/test/staking/Staking.test.ts similarity index 98% rename from test/staking/DPoStaking.test.ts rename to test/staking/Staking.test.ts index c46a3d893..183970cfd 100644 --- a/test/staking/DPoStaking.test.ts +++ b/test/staking/Staking.test.ts @@ -5,6 +5,7 @@ import { ethers, network } from 'hardhat'; import { Staking, Staking__factory, TransparentUpgradeableProxy__factory } from '../../src/types'; import { MockValidatorSet__factory } from '../../src/types/factories/MockValidatorSet__factory'; +import { StakingVesting__factory } from '../../src/types/factories/StakingVesting__factory'; import { MockValidatorSet } from '../../src/types/MockValidatorSet'; const EPS = 1; @@ -93,11 +94,13 @@ describe('Staking test', () => { before(async () => { [deployer, proxyAdmin, userA, userB, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); validatorCandidates = validatorCandidates.slice(0, 3); + const stakingVestingContract = await new StakingVesting__factory(deployer).deploy(); const nonce = await deployer.getTransactionCount(); const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); validatorContract = await new MockValidatorSet__factory(deployer).deploy( stakingContractAddr, ethers.constants.AddressZero, + stakingVestingContract.address, 10, 2 ); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index db663fd61..7c9333d8c 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -13,6 +13,7 @@ import { MockSlashIndicator__factory, } from '../../src/types'; import * as RoninValidatorSet from '../../src/script/ronin-validator-set'; +import { StakingVesting__factory } from '../../src/types/factories/StakingVesting__factory'; let roninValidatorSet: MockRoninValidatorSetEpochSetter; let stakingContract: Staking; @@ -46,8 +47,15 @@ describe('Ronin Validator Set test', () => { await network.provider.send('hardhat_setCoinbase', [coinbase.address]); const nonce = await deployer.getTransactionCount(); - const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 1 }); - const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 3 }); + const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 3 }); + const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 5 }); + + const stakingVestingLogic = await new StakingVesting__factory(deployer).deploy(); + const stakingVesting = await new TransparentUpgradeableProxy__factory(deployer).deploy( + stakingVestingLogic.address, + proxyAdmin.address, + stakingVestingLogic.interface.encodeFunctionData('initialize', [0, roninValidatorSetAddr]) + ); slashIndicator = await new MockSlashIndicator__factory(deployer).deploy( roninValidatorSetAddr, @@ -60,6 +68,7 @@ describe('Ronin Validator Set test', () => { governanceAdmin.address, slashIndicator.address, stakingContractAddr, + stakingVesting.address, maxValidatorNumber ); await roninValidatorSet.deployed(); From 2ad8aac7fcfb164df79f63701c61a7114e2a2996 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 13 Sep 2022 16:10:21 +0700 Subject: [PATCH 099/190] [Staking] Fix contract events and add test cases (#6) ### Description - Mainly adds test cases for emitted events in staking contract - Renames `candidateOwner` to `candidateAdmin` for Staking contract struct - Fixes events while claiming the reward - Exposes method `getValidatorCandidateLength` --- contracts/interfaces/IStaking.sol | 12 +- contracts/staking/RewardCalculation.sol | 25 +- contracts/staking/Staking.sol | 18 +- contracts/staking/StakingManager.sol | 37 +- package.json | 3 +- src/deploy/proxy/ronin-validator-proxy.ts | 8 +- src/utils.ts | 27 +- .../helpers}/ronin-validator-set.ts | 60 +-- test/helpers/staking.ts | 26 ++ test/helpers/utils.ts | 26 ++ test/staking/CoreStaking.test.ts | 370 ++++++++++++++---- test/staking/Staking.test.ts | 27 +- test/validator/RoninValidatorSet.test.ts | 6 +- 13 files changed, 447 insertions(+), 198 deletions(-) rename {src/script => test/helpers}/ronin-validator-set.ts (60%) create mode 100644 test/helpers/staking.ts create mode 100644 test/helpers/utils.ts diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 081a0bc5e..37527fd84 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -35,12 +35,7 @@ interface IStaking is IRewardPool { } /// @dev Emitted when the validator candidate is proposed. - event ValidatorProposed( - address indexed consensusAddr, - address indexed candidateIdx, - uint256 amount, - ValidatorCandidate _info - ); + event ValidatorProposed(address indexed consensusAddr, address indexed candidateAdmin, uint256 indexed candidateIdx); /// @dev Emitted when the candidate admin staked for themself. event Staked(address indexed validator, uint256 amount); /// @dev Emitted when the candidate admin unstaked the amount of RON from themself. @@ -112,6 +107,11 @@ interface IStaking is IRewardPool { */ function getValidatorCandidates() external view returns (ValidatorCandidate[] memory candidates); + /** + * @dev Returns the current candidate length. + */ + function getValidatorCandidateLength() external view returns (uint256); + /** * @dev Returns the validator candidate weights. */ diff --git a/contracts/staking/RewardCalculation.sol b/contracts/staking/RewardCalculation.sol index 974ff9df7..7875f2bd2 100644 --- a/contracts/staking/RewardCalculation.sol +++ b/contracts/staking/RewardCalculation.sol @@ -49,14 +49,11 @@ abstract contract RewardCalculation is IRewardPool { if (_reward.lastSyncedBlock <= _sPool.lastSyncedBlock) { uint256 _currentBalance = balanceOf(_poolAddr, _user); - _sReward.debited = (_currentBalance * _sPool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; - _sReward.balance = _currentBalance; - _sReward.accumulatedRps = _sPool.accumulatedRps; + return (_currentBalance * _sPool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; } - uint256 _balance = _sReward.balance; - uint256 _credited = (_balance * _sReward.accumulatedRps) / 1e18; - return (_balance * _sPool.accumulatedRps) / 1e18 + _sReward.debited - _credited; + uint256 _diffRps = _sPool.accumulatedRps - _sReward.accumulatedRps; + return (_sReward.balance * _diffRps) / 1e18 + _sReward.debited; } /** @@ -126,17 +123,21 @@ abstract contract RewardCalculation is IRewardPool { function _claimReward(address _poolAddr, address _user) internal returns (uint256 _amount) { _amount = getClaimableReward(_poolAddr, _user); emit RewardClaimed(_poolAddr, _user, _amount); - SettledPool memory _sPool = _settledPool[_poolAddr]; + SettledPool memory _sPool = _settledPool[_poolAddr]; PendingRewardFields storage _reward = _pUserReward[_poolAddr][_user]; - _reward.credited += _amount; - _reward.lastSyncedBlock = block.number; - emit PendingRewardUpdated(_poolAddr, _user, _reward.debited, _reward.credited); - SettledRewardFields storage _sReward = _sUserReward[_poolAddr][_user]; + _sReward.debited = 0; - _sReward.accumulatedRps = _sPool.accumulatedRps; + if (_reward.lastSyncedBlock <= _sPool.lastSyncedBlock) { + _sReward.balance = balanceOf(_poolAddr, _user); + _sReward.accumulatedRps = _sPool.accumulatedRps; + } emit SettledRewardUpdated(_poolAddr, _user, _sReward.balance, 0, _sPool.accumulatedRps); + + _reward.credited += _amount; + _reward.lastSyncedBlock = block.number; + emit PendingRewardUpdated(_poolAddr, _user, _reward.debited, _reward.credited); } /** diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 1600a23b3..69028d890 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -98,6 +98,13 @@ contract Staking is IStaking, StakingManager, Initializable { _setMaxValidatorCandidate(_threshold); } + /** + * @inheritdoc IStaking + */ + function getValidatorCandidateLength() public view override(IStaking, StakingManager) returns (uint256) { + return validatorCandidates.length; + } + /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR VALIDATOR // /////////////////////////////////////////////////////////////////////////////////////// @@ -206,13 +213,6 @@ contract Staking is IStaking, StakingManager, Initializable { _candidateIndex[_consensusAddr] = _candidateIdx; } - /** - * @inheritdoc StakingManager - */ - function _getValidatorCandidateLength() internal view override returns (uint256) { - return validatorCandidates.length; - } - /** * @dev Sets the governance admin address. * @@ -279,13 +279,13 @@ contract Staking is IStaking, StakingManager, Initializable { */ function _createValidatorCandidate( address _consensusAddr, - address _candidateOwner, + address _candidateAdmin, address payable _treasuryAddr, uint256 _commissionRate ) internal virtual override returns (ValidatorCandidate memory) { ValidatorCandidate storage _candidate = validatorCandidates.push(); _candidate.consensusAddr = _consensusAddr; - _candidate.candidateAdmin = _candidateOwner; + _candidate.candidateAdmin = _candidateAdmin; _candidate.treasuryAddr = _treasuryAddr; _candidate.commissionRate = _commissionRate; return _candidate; diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index e8e471f9a..f1c2ebeb7 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -14,7 +14,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { _; } - modifier notCandidateOwner(address _consensusAddr) { + modifier notCandidateAdmin(address _consensusAddr) { ValidatorCandidate memory _candidate = _getCandidate(_consensusAddr); require(msg.sender != _candidate.candidateAdmin, "StakingManager: method caller must not be the candidate admin"); _; @@ -50,6 +50,11 @@ abstract contract StakingManager is IStaking, RewardCalculation { */ function maxValidatorCandidate() public view virtual returns (uint256); + /** + * @dev IStaking + */ + function getValidatorCandidateLength() public view virtual returns (uint256); + /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR VALIDATOR CANDIDATE // /////////////////////////////////////////////////////////////////////////////////////// @@ -112,9 +117,9 @@ abstract contract StakingManager is IStaking, RewardCalculation { address payable _treasuryAddr, uint256 _commissionRate, uint256 _amount, - address _candidateOwner + address _candidateAdmin ) internal returns (uint256 _candidateIdx) { - uint256 _length = _getValidatorCandidateLength(); + uint256 _length = getValidatorCandidateLength(); require(_length < maxValidatorCandidate(), "StakingManager: query for exceeded validator array length"); require(_getCandidateIndex(_consensusAddr) == 0, "StakingManager: query for existed candidate"); require(_amount >= minValidatorBalance(), "StakingManager: insufficient amount"); @@ -123,14 +128,9 @@ abstract contract StakingManager is IStaking, RewardCalculation { _candidateIdx = ~_length; _setCandidateIndex(_consensusAddr, _candidateIdx); - ValidatorCandidate memory _candidate = _createValidatorCandidate( - _consensusAddr, - _candidateOwner, - _treasuryAddr, - _commissionRate - ); - - emit ValidatorProposed(_consensusAddr, _candidateOwner, _amount, _candidate); + _createValidatorCandidate(_consensusAddr, _candidateAdmin, _treasuryAddr, _commissionRate); + + emit ValidatorProposed(_consensusAddr, _candidateAdmin, _length); } /** @@ -190,14 +190,14 @@ abstract contract StakingManager is IStaking, RewardCalculation { /** * @inheritdoc IStaking */ - function delegate(address _consensusAddr) external payable noEmptyValue notCandidateOwner(_consensusAddr) { + function delegate(address _consensusAddr) external payable noEmptyValue notCandidateAdmin(_consensusAddr) { _delegate(_consensusAddr, msg.sender, msg.value); } /** * @inheritdoc IStaking */ - function undelegate(address _consensusAddr, uint256 _amount) external notCandidateOwner(_consensusAddr) { + function undelegate(address _consensusAddr, uint256 _amount) external notCandidateAdmin(_consensusAddr) { address payable _delegator = payable(msg.sender); _undelegate(_consensusAddr, _delegator, _amount); // TODO(Thor): replace by `call` and use reentrancy gruard @@ -211,7 +211,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { address _consensusAddrSrc, address _consensusAddrDst, uint256 _amount - ) external notCandidateOwner(_consensusAddrDst) { + ) external notCandidateAdmin(_consensusAddrDst) { address _delegator = msg.sender; _undelegate(_consensusAddrSrc, _delegator, _amount); _delegate(_consensusAddrDst, _delegator, _amount); @@ -251,7 +251,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { function delegateRewards(address[] calldata _consensusAddrList, address _consensusAddrDst) external override - notCandidateOwner(_consensusAddrDst) + notCandidateAdmin(_consensusAddrDst) returns (uint256 _amount) { return _delegateRewards(msg.sender, _consensusAddrList, _consensusAddrDst); @@ -362,17 +362,12 @@ abstract contract StakingManager is IStaking, RewardCalculation { */ function _setCandidateIndex(address _consensusAddr, uint256 _candidateIdx) internal virtual; - /** - * @dev Returns the current validator length. - */ - function _getValidatorCandidateLength() internal view virtual returns (uint256); - /** * @dev Creates new validator candidate in the storage and returns its struct. */ function _createValidatorCandidate( address _consensusAddr, - address _candidateOwner, + address _candidateAdmin, address payable _treasuryAddr, uint256 _commissionRate ) internal virtual returns (ValidatorCandidate memory); diff --git a/package.json b/package.json index 73d9e77cc..f0ab2ad8c 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,7 @@ }, "lint-staged": { "contracts/**/*.sol": "prettier --write", - "{test,src}/!(types)/*.{js,ts}": "prettier --write", - "src/*.{js,ts}": "prettier --write", + "{test,src}/**/*.{js,ts}": "prettier --write", "hardhat.config.{js,ts}": "prettier --write" }, "dependencies": { diff --git a/src/deploy/proxy/ronin-validator-proxy.ts b/src/deploy/proxy/ronin-validator-proxy.ts index c877a38e4..c2440e4ad 100644 --- a/src/deploy/proxy/ronin-validator-proxy.ts +++ b/src/deploy/proxy/ronin-validator-proxy.ts @@ -29,6 +29,12 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme }; deploy.tags = ['RoninValidatorSetProxy']; -deploy.dependencies = ['ProxyAdmin', 'RoninValidatorSetLogic', 'SlashIndicatorProxy', 'StakingProxy', 'StakingVestingProxy']; +deploy.dependencies = [ + 'ProxyAdmin', + 'RoninValidatorSetLogic', + 'SlashIndicatorProxy', + 'StakingProxy', + 'StakingVestingProxy', +]; export default deploy; diff --git a/src/utils.ts b/src/utils.ts index 19d83cd16..e733e8453 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,4 @@ -import { expect } from 'chai'; -import { BigNumber, ContractTransaction } from 'ethers'; -import { Interface, LogDescription } from 'ethers/lib/utils'; +import { BigNumber } from 'ethers'; export const DEFAULT_ADDRESS = '0x0000000000000000000000000000000000000000'; export const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000'; @@ -11,26 +9,3 @@ export const randomBigNumber = () => { .join(''); return BigNumber.from(`0x${hexString}`); }; - -export const expectEvent = async ( - contractInterface: Interface, - eventName: string, - tx: ContractTransaction, - expectFn: (log: LogDescription) => void, - eventNumbers?: number -) => { - const receipt = await tx.wait(); - const topic = contractInterface.getEventTopic(eventName); - let counter = 0; - - for (let i = 0; i < receipt.logs.length; i++) { - const eventLog = receipt.logs[i]; - if (eventLog.topics[0] == topic) { - counter++; - const event = contractInterface.parseLog(eventLog); - expectFn(event); - } - } - - expect(counter, 'invalid number of emitted events').eq(eventNumbers); -}; diff --git a/src/script/ronin-validator-set.ts b/test/helpers/ronin-validator-set.ts similarity index 60% rename from src/script/ronin-validator-set.ts rename to test/helpers/ronin-validator-set.ts index accc97fb3..d69fd213a 100644 --- a/src/script/ronin-validator-set.ts +++ b/test/helpers/ronin-validator-set.ts @@ -1,24 +1,24 @@ import { expect } from 'chai'; import { BigNumberish, ContractTransaction } from 'ethers'; -import { expectEvent } from '../utils'; -import { RoninValidatorSet__factory } from '../types'; +import { expectEvent } from './utils'; +import { RoninValidatorSet__factory } from '../../src/types'; const contractInterface = RoninValidatorSet__factory.createInterface(); export const expects = { emitRewardDeprecatedEvent: async function ( tx: ContractTransaction, - expectedCoinbaseAddr: string, - expectedDeprecatedReward: BigNumberish + expectingCoinbaseAddr: string, + expectingDeprecatedReward: BigNumberish ) { - expectEvent( + await expectEvent( contractInterface, 'RewardDeprecated', tx, (event) => { - expect(event.args[0], 'invalid coinbase address').eq(expectedCoinbaseAddr); - expect(event.args[1], 'invalid reward').eq(expectedDeprecatedReward); + expect(event.args[0], 'invalid coinbase address').eq(expectingCoinbaseAddr); + expect(event.args[1], 'invalid reward').eq(expectingDeprecatedReward); }, 1 ); @@ -26,16 +26,16 @@ export const expects = { emitBlockRewardSubmittedEvent: async function ( tx: ContractTransaction, - expectedCoinbaseAddr: string, - expectedDeprecatedReward: BigNumberish + expectingCoinbaseAddr: string, + expectingDeprecatedReward: BigNumberish ) { - expectEvent( + await expectEvent( contractInterface, 'BlockRewardSubmitted', tx, (event) => { - expect(event.args[0], 'invalid coinbase address').eq(expectedCoinbaseAddr); - expect(event.args[1], 'invalid reward').eq(expectedDeprecatedReward); + expect(event.args[0], 'invalid coinbase address').eq(expectingCoinbaseAddr); + expect(event.args[1], 'invalid reward').eq(expectingDeprecatedReward); }, 1 ); @@ -43,18 +43,18 @@ export const expects = { emitValidatorSlashedEvent: async function ( tx: ContractTransaction, - expectedValidatorAddr: string, - expectedJailedUntil: BigNumberish, - expectedDeductedStakingAmount: BigNumberish + expectingValidatorAddr: string, + expectingJailedUntil: BigNumberish, + expectingDeductedStakingAmount: BigNumberish ) { - expectEvent( + await expectEvent( contractInterface, 'ValidatorSlashed', tx, (event) => { - expect(event.args[0], 'invalid coinbase address').eq(expectedValidatorAddr); - expect(event.args[1], 'invalid jailed until').eq(expectedJailedUntil); - expect(event.args[2], 'invalid deducted staking amount').eq(expectedDeductedStakingAmount); + expect(event.args[0], 'invalid coinbase address').eq(expectingValidatorAddr); + expect(event.args[1], 'invalid jailed until').eq(expectingJailedUntil); + expect(event.args[2], 'invalid deducted staking amount').eq(expectingDeductedStakingAmount); }, 1 ); @@ -62,40 +62,40 @@ export const expects = { emitMiningRewardDistributedEvent: async function ( tx: ContractTransaction, - expectedCoinbaseAddr: string, - expectedAmount: BigNumberish + expectingCoinbaseAddr: string, + expectingAmount: BigNumberish ) { - expectEvent( + await expectEvent( contractInterface, 'MiningRewardDistributed', tx, (event) => { - expect(event.args[0], 'invalid coinbase address').eq(expectedCoinbaseAddr); - expect(event.args[1], 'invalid amount').eq(expectedAmount); + expect(event.args[0], 'invalid coinbase address').eq(expectingCoinbaseAddr); + expect(event.args[1], 'invalid amount').eq(expectingAmount); }, 1 ); }, - emitStakingRewardDistributedEvent: async function (tx: ContractTransaction, expectedAmount: BigNumberish) { - expectEvent( + emitStakingRewardDistributedEvent: async function (tx: ContractTransaction, expectingAmount: BigNumberish) { + await expectEvent( contractInterface, 'StakingRewardDistributed', tx, (event) => { - expect(event.args[0], 'invalid deprecated reward').eq(expectedAmount); + expect(event.args[0], 'invalid deprecated reward').eq(expectingAmount); }, 1 ); }, - emitValidatorSetUpdatedEvent: async function (tx: ContractTransaction, expectedValidators: string[]) { - expectEvent( + emitValidatorSetUpdatedEvent: async function (tx: ContractTransaction, expectingValidators: string[]) { + await expectEvent( contractInterface, 'ValidatorSetUpdated', tx, (event) => { - expect(event.args[0], 'invalid validator set').have.deep.members(expectedValidators); + expect(event.args[0], 'invalid validator set').have.deep.members(expectingValidators); }, 1 ); diff --git a/test/helpers/staking.ts b/test/helpers/staking.ts new file mode 100644 index 000000000..112814bf5 --- /dev/null +++ b/test/helpers/staking.ts @@ -0,0 +1,26 @@ +import { expect } from 'chai'; +import { BigNumberish, ContractTransaction } from 'ethers'; + +import { expectEvent } from './utils'; +import { Staking__factory } from '../../src/types'; + +const contractInterface = Staking__factory.createInterface(); + +export const expects = { + emitSettledPoolsUpdatedEvent: async function ( + tx: ContractTransaction, + expectingPoolAddressList: string[], + expectingAccumulatedRpsList: BigNumberish[] + ) { + await expectEvent( + contractInterface, + 'SettledPoolsUpdated', + tx, + (event) => { + expect(event.args[0], 'invalid pool address list').eql(expectingPoolAddressList); + expect(event.args[1], 'invalid accumulated rps list').eql(expectingAccumulatedRpsList); + }, + 1 + ); + }, +}; diff --git a/test/helpers/utils.ts b/test/helpers/utils.ts new file mode 100644 index 000000000..54df11a04 --- /dev/null +++ b/test/helpers/utils.ts @@ -0,0 +1,26 @@ +import { expect } from 'chai'; +import { ContractTransaction } from 'ethers'; +import { Interface, LogDescription } from 'ethers/lib/utils'; + +export const expectEvent = async ( + contractInterface: Interface, + eventName: string, + tx: ContractTransaction, + expectFn: (log: LogDescription) => void, + eventNumbers?: number +) => { + const receipt = await tx.wait(); + const topic = contractInterface.getEventTopic(eventName); + let counter = 0; + + for (let i = 0; i < receipt.logs.length; i++) { + const eventLog = receipt.logs[i]; + if (eventLog.topics[0] == topic) { + counter++; + const event = contractInterface.parseLog(eventLog); + expectFn(event); + } + } + + expect(counter, 'invalid number of emitted events').eq(eventNumbers); +}; diff --git a/test/staking/CoreStaking.test.ts b/test/staking/CoreStaking.test.ts index 85cf023c4..5f095ca4f 100644 --- a/test/staking/CoreStaking.test.ts +++ b/test/staking/CoreStaking.test.ts @@ -1,11 +1,13 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { expect } from 'chai'; -import { BigNumber, BigNumberish } from 'ethers'; +import { BigNumber, BigNumberish, ContractTransaction } from 'ethers'; import { ethers, network } from 'hardhat'; import { MockStaking, MockStaking__factory } from '../../src/types'; +import * as StakingContract from '../helpers/staking'; const EPS = 1; +const MASK = BigNumber.from(10).pow(18); const poolAddr = ethers.constants.AddressZero; let deployer: SignerWithAddress; @@ -14,34 +16,48 @@ let userB: SignerWithAddress; let stakingContract: MockStaking; const local = { + balanceA: BigNumber.from(0), + balanceB: BigNumber.from(0), accumulatedRewardForA: BigNumber.from(0), accumulatedRewardForB: BigNumber.from(0), claimableRewardForA: BigNumber.from(0), claimableRewardForB: BigNumber.from(0), + aRps: BigNumber.from(0), + settledARps: BigNumber.from(0), + syncBalance: async function () { + this.balanceA = await stakingContract.balanceOf(poolAddr, userA.address); + this.balanceB = await stakingContract.balanceOf(poolAddr, userB.address); + }, recordReward: async function (reward: BigNumberish) { const totalStaked = await stakingContract.totalBalance(poolAddr); - const stakingAmountA = await stakingContract.balanceOf(poolAddr, userA.address); - const stakingAmountB = await stakingContract.balanceOf(poolAddr, userB.address); + await this.syncBalance(); this.accumulatedRewardForA = this.accumulatedRewardForA.add( - BigNumber.from(reward).mul(stakingAmountA).div(totalStaked) + BigNumber.from(reward).mul(this.balanceA).div(totalStaked) ); this.accumulatedRewardForB = this.accumulatedRewardForB.add( - BigNumber.from(reward).mul(stakingAmountB).div(totalStaked) + BigNumber.from(reward).mul(this.balanceB).div(totalStaked) ); + this.aRps = this.aRps.add(BigNumber.from(reward).mul(MASK).div(totalStaked)); }, commitRewardPool: function () { this.claimableRewardForA = this.accumulatedRewardForA; this.claimableRewardForB = this.accumulatedRewardForB; + this.settledARps = this.aRps; }, slash: function () { this.accumulatedRewardForA = this.claimableRewardForA; this.accumulatedRewardForB = this.claimableRewardForB; + this.aRps = this.settledARps; }, reset: function () { this.claimableRewardForA = BigNumber.from(0); this.claimableRewardForB = BigNumber.from(0); this.accumulatedRewardForA = BigNumber.from(0); this.accumulatedRewardForB = BigNumber.from(0); + this.aRps = BigNumber.from(0); + this.settledARps = BigNumber.from(0); + this.balanceA = BigNumber.from(0); + this.balanceB = BigNumber.from(0); }, claimRewardForA: function () { this.accumulatedRewardForA = this.accumulatedRewardForA.sub(this.claimableRewardForA); @@ -81,6 +97,9 @@ const expectLocalCalculationRight = async () => { }; describe('Core Staking test', () => { + let tx: ContractTransaction; + const txs: ContractTransaction[] = []; + before(async () => { [deployer, userA, userB] = await ethers.getSigners(); stakingContract = await new MockStaking__factory(deployer).deploy(poolAddr); @@ -93,79 +112,112 @@ describe('Core Staking test', () => { }); it('Should work properly with staking actions occurring sequentially for a normal period', async () => { - await stakingContract.stake(userA.address, 100); - await stakingContract.stake(userB.address, 100); + txs[0] = await stakingContract.stake(userA.address, 100); + txs[1] = await stakingContract.stake(userB.address, 100); await network.provider.send('evm_mine'); + await expect(txs[0]!).emit(stakingContract, 'PendingRewardUpdated').withArgs(poolAddr, userA.address, 0, 0); + await expect(txs[1]!).emit(stakingContract, 'PendingRewardUpdated').withArgs(poolAddr, userB.address, 0, 0); - await stakingContract.recordReward(1000); + tx = await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); - await stakingContract.recordReward(1000); + tx = await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await network.provider.send('evm_mine'); await network.provider.send('evm_mine'); await local.recordReward(1000); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); - await stakingContract.stake(userA.address, 200); + txs[0] = await stakingContract.stake(userA.address, 200); await network.provider.send('evm_mine'); + await local.syncBalance(); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 1000, local.aRps.mul(local.balanceA).div(MASK)); await expectLocalCalculationRight(); - await stakingContract.recordReward(1000); + tx = await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); - await stakingContract.unstake(userA.address, 200); - await stakingContract.recordReward(1000); + txs[0] = await stakingContract.unstake(userA.address, 200); + tx = await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); + await local.syncBalance(); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 1750, local.aRps.mul(local.balanceA).div(MASK)); await local.recordReward(1000); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); - await stakingContract.stake(userA.address, 200); + txs[0] = await stakingContract.stake(userA.address, 200); await network.provider.send('evm_mine'); - await local.recordReward(0); + await local.syncBalance(); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK)); await expectLocalCalculationRight(); - await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + tx = await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); + await StakingContract.expects.emitSettledPoolsUpdatedEvent(tx!, [poolAddr], [local.aRps]); local.commitRewardPool(); await expectLocalCalculationRight(); }); it('Should work properly with staking actions occurring sequentially for a slashed period', async () => { - await stakingContract.stake(userA.address, 100); + txs[0] = await stakingContract.stake(userA.address, 100); await network.provider.send('evm_mine'); await expectLocalCalculationRight(); - await local.recordReward(0); + await expect(txs[0]!) + .emit(stakingContract, 'SettledRewardUpdated') + .withArgs(poolAddr, userA.address, local.balanceA, 2250, local.aRps); + await local.syncBalance(); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK)); - await stakingContract.recordReward(1000); + tx = await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); - await stakingContract.recordReward(1000); + tx = await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); await expectLocalCalculationRight(); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await stakingContract.stake(userA.address, 300); - await stakingContract.recordReward(1000); + txs[0] = await stakingContract.stake(userA.address, 300); + tx = await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); + await local.syncBalance(); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 3850, local.aRps.mul(local.balanceA).div(MASK)); await local.recordReward(1000); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); - await stakingContract.slash(); + tx = await stakingContract.slash(); await network.provider.send('evm_mine'); local.slash(); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); - await stakingContract.recordReward(0); + tx = await stakingContract.recordReward(0); await network.provider.send('evm_mine'); await local.recordReward(0); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); await network.provider.send('evm_mine'); @@ -173,50 +225,83 @@ describe('Core Staking test', () => { await network.provider.send('evm_mine'); await network.provider.send('evm_mine'); - await stakingContract.unstake(userA.address, 300); + txs[0] = await stakingContract.unstake(userA.address, 300); await network.provider.send('evm_mine'); - await stakingContract.unstake(userA.address, 100); + await local.syncBalance(); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK)); + txs[0] = await stakingContract.unstake(userA.address, 100); await network.provider.send('evm_mine'); await expectLocalCalculationRight(); + await local.syncBalance(); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK)); - await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + tx = await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await expectLocalCalculationRight(); + await StakingContract.expects.emitSettledPoolsUpdatedEvent(tx!, [poolAddr], [local.aRps]); }); it('Should work properly with staking actions occurring sequentially for a slashed period again', async () => { - await stakingContract.stake(userA.address, 100); - await network.provider.send('evm_mine'); - await local.recordReward(0); - await expectLocalCalculationRight(); - - await stakingContract.claimReward(userA.address); - await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); + txs[0] = await stakingContract.stake(userA.address, 100); + await network.provider.send('evm_mine'); + await expect(txs[0]!) + .emit(stakingContract, 'SettledRewardUpdated') + .withArgs(poolAddr, userA.address, local.balanceA, local.claimableRewardForA, local.settledARps); + await local.syncBalance(); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK)); + await expectLocalCalculationRight(); + + txs[0] = await stakingContract.claimReward(userA.address); + tx = await stakingContract.recordReward(1000); + await network.provider.send('evm_mine'); + await expect(txs[0]!) + .emit(stakingContract, 'RewardClaimed') + .withArgs(poolAddr, userA.address, local.claimableRewardForA); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK).add(local.claimableRewardForA)); + await expect(txs[0]!) + .emit(stakingContract, 'SettledRewardUpdated') + .withArgs(poolAddr, userA.address, 300, 0, local.settledARps); local.claimRewardForA(); + await local.recordReward(1000); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); - await stakingContract.recordReward(1000); + tx = await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); - await stakingContract.stake(userA.address, 300); - await stakingContract.recordReward(1000); + txs[0] = await stakingContract.stake(userA.address, 300); + tx = await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); + await local.syncBalance(); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 1600, local.aRps.mul(local.balanceA).div(MASK)); await local.recordReward(1000); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); - await stakingContract.slash(); + tx = await stakingContract.slash(); await network.provider.send('evm_mine'); local.slash(); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); - await stakingContract.recordReward(0); + tx = await stakingContract.recordReward(0); await network.provider.send('evm_mine'); await local.recordReward(0); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); await network.provider.send('evm_mine'); @@ -224,100 +309,223 @@ describe('Core Staking test', () => { await network.provider.send('evm_mine'); await network.provider.send('evm_mine'); - await stakingContract.unstake(userA.address, 300); + txs[0] = await stakingContract.unstake(userA.address, 300); await network.provider.send('evm_mine'); - await stakingContract.unstake(userA.address, 100); + await local.syncBalance(); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 0, local.aRps.mul(local.balanceA).div(MASK)); + + txs[0] = await stakingContract.unstake(userA.address, 100); await network.provider.send('evm_mine'); + await local.syncBalance(); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 0, local.aRps.mul(local.balanceA).div(MASK)); await expectLocalCalculationRight(); - await stakingContract.claimReward(userB.address); - await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + txs[1] = await stakingContract.claimReward(userB.address); + tx = await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); + await expect(txs[1]!) + .emit(stakingContract, 'RewardClaimed') + .withArgs(poolAddr, userB.address, local.claimableRewardForB); + await expect(txs[1]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userB.address, 0, local.aRps.mul(local.balanceB).div(MASK)); + await expect(txs[1]!) + .emit(stakingContract, 'SettledRewardUpdated') + .withArgs(poolAddr, userB.address, local.balanceB, 0, local.settledARps); local.claimRewardForB(); + await StakingContract.expects.emitSettledPoolsUpdatedEvent(tx!, [poolAddr], [local.aRps]); + local.commitRewardPool(); await expectLocalCalculationRight(); }); it('Should be able to calculate right reward after claiming', async () => { - await stakingContract.recordReward(1000); - await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + const lastCredited = local.aRps.mul(300).div(MASK); + + txs[0] = await stakingContract.recordReward(1000); + txs[1] = await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await local.recordReward(1000); + await expect(txs[0]!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); + await StakingContract.expects.emitSettledPoolsUpdatedEvent(txs[1]!, [poolAddr], [local.aRps]); local.commitRewardPool(); await expectLocalCalculationRight(); - await stakingContract.claimReward(userA.address); - await stakingContract.recordReward(1000); + txs[0] = await stakingContract.claimReward(userA.address); + tx = await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); - await local.recordReward(1000); + await expect(txs[0]!) + .emit(stakingContract, 'RewardClaimed') + .withArgs(poolAddr, userA.address, local.claimableRewardForA); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 0, lastCredited.add(local.claimableRewardForA)); + await expect(txs[0]!) + .emit(stakingContract, 'SettledRewardUpdated') + .withArgs(poolAddr, userA.address, 300, 0, local.aRps); local.claimRewardForA(); + await local.recordReward(1000); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); - await stakingContract.claimReward(userA.address); + txs[0] = await stakingContract.claimReward(userA.address); await network.provider.send('evm_mine'); local.claimRewardForA(); await expectLocalCalculationRight(); + await expect(txs[0]!).emit(stakingContract, 'RewardClaimed').withArgs(poolAddr, userA.address, 0); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 0, lastCredited.add(750)); + await expect(txs[0]!) + .emit(stakingContract, 'SettledRewardUpdated') + .withArgs(poolAddr, userA.address, 300, 0, local.settledARps); - await stakingContract.claimReward(userB.address); - await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + txs[1] = await stakingContract.claimReward(userB.address); + tx = await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); + await expect(txs[1]!).emit(stakingContract, 'RewardClaimed').withArgs(poolAddr, userB.address, 250); + await expect(txs[1]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userB.address, 0, 1750 + 250); + await expect(txs[1]!) + .emit(stakingContract, 'SettledRewardUpdated') + .withArgs(poolAddr, userB.address, 100, 0, local.settledARps); local.claimRewardForB(); local.commitRewardPool(); + await StakingContract.expects.emitSettledPoolsUpdatedEvent(tx!, [poolAddr], [local.settledARps]); await expectLocalCalculationRight(); }); it('Should work properly with staking actions from multi-users occurring in the same block', async () => { - await stakingContract.stake(userA.address, 100); - await network.provider.send('evm_mine'); - - await stakingContract.stake(userA.address, 300); - await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); + txs[0] = await stakingContract.stake(userA.address, 100); + await network.provider.send('evm_mine'); + await expect(txs[0]!) + .emit(stakingContract, 'SettledRewardUpdated') + .withArgs(poolAddr, userA.address, local.balanceA, local.claimableRewardForA, local.settledARps); + await local.syncBalance(); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, local.claimableRewardForA, local.aRps.mul(local.balanceA).div(MASK)); + + txs[0] = await stakingContract.stake(userA.address, 300); + tx = await stakingContract.recordReward(1000); + await network.provider.send('evm_mine'); + await local.syncBalance(); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 750, local.aRps.mul(local.balanceA).div(MASK)); await local.recordReward(1000); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); - await stakingContract.stake(userB.address, 200); - await stakingContract.recordReward(1000); + txs[1] = await stakingContract.stake(userB.address, 200); + tx = await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); + await expect(txs[1]!) + .emit(stakingContract, 'SettledRewardUpdated') + .withArgs(poolAddr, userB.address, local.balanceB, local.claimableRewardForB, local.settledARps); await local.recordReward(1000); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); - await stakingContract.recordReward(1000); + tx = await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); - await stakingContract.recordReward(1000); + tx = await stakingContract.recordReward(1000); await network.provider.send('evm_mine'); await local.recordReward(1000); - await expectLocalCalculationRight(); - - await stakingContract.unstake(userB.address, 200); - await stakingContract.unstake(userA.address, 400); - await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); + await expectLocalCalculationRight(); + + txs[1] = await stakingContract.unstake(userB.address, 200); + txs[0] = await stakingContract.unstake(userA.address, 400); + tx = await stakingContract.recordReward(1000); + await network.provider.send('evm_mine'); + await local.syncBalance(); + let lastCreditedB = local.aRps.mul(local.balanceB).div(MASK); + let lastCreditedA = local.aRps.mul(local.balanceA).div(MASK); + await expect(txs[1]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userB.address, local.accumulatedRewardForB, lastCreditedB); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 3725, lastCreditedA); await local.recordReward(1000); - await expectLocalCalculationRight(); - - await stakingContract.claimReward(userA.address); - await stakingContract.claimReward(userB.address); - await network.provider.send('evm_mine'); + await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); + await expectLocalCalculationRight(); + + txs[0] = await stakingContract.claimReward(userA.address); + txs[1] = await stakingContract.claimReward(userB.address); + await network.provider.send('evm_mine'); + lastCreditedA = lastCreditedA.add(local.claimableRewardForA); + await expect(txs[0]!) + .emit(stakingContract, 'RewardClaimed') + .withArgs(poolAddr, userA.address, local.claimableRewardForA); + await expect(txs[0]!) + .emit(stakingContract, 'SettledRewardUpdated') + .withArgs(poolAddr, userA.address, local.balanceA, 0, local.settledARps); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 3725, lastCreditedA); + lastCreditedB = lastCreditedB.add(local.claimableRewardForB); + await expect(txs[1]!) + .emit(stakingContract, 'RewardClaimed') + .withArgs(poolAddr, userB.address, local.claimableRewardForB); + await expect(txs[1]!) + .emit(stakingContract, 'SettledRewardUpdated') + .withArgs(poolAddr, userB.address, local.balanceB, 0, local.settledARps); + await expect(txs[1]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userB.address, 1275, lastCreditedB); local.claimRewardForA(); local.claimRewardForB(); await expectLocalCalculationRight(); - await stakingContract.unstake(userA.address, 200); - await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + txs[0] = await stakingContract.unstake(userA.address, 200); + tx = await stakingContract.commitRewardPool([ethers.constants.AddressZero]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); + await local.syncBalance(); + lastCreditedA = local.balanceA.mul(local.aRps).div(MASK); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, local.accumulatedRewardForA, lastCreditedA); local.commitRewardPool(); - await expectLocalCalculationRight(); - - await stakingContract.claimReward(userA.address); - await stakingContract.claimReward(userB.address); - await network.provider.send('evm_mine'); + await StakingContract.expects.emitSettledPoolsUpdatedEvent(tx!, [poolAddr], [local.settledARps]); + await expectLocalCalculationRight(); + + txs[0] = await stakingContract.claimReward(userA.address); + txs[1] = await stakingContract.claimReward(userB.address); + await network.provider.send('evm_mine'); + lastCreditedA = lastCreditedA.add(local.claimableRewardForA); + await expect(txs[0]!) + .emit(stakingContract, 'RewardClaimed') + .withArgs(poolAddr, userA.address, local.claimableRewardForA); + await expect(txs[0]!) + .emit(stakingContract, 'SettledRewardUpdated') + .withArgs(poolAddr, userA.address, local.balanceA, 0, local.settledARps); + await expect(txs[0]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userA.address, 3725, lastCreditedA); + lastCreditedB = lastCreditedB.add(local.claimableRewardForB); + await expect(txs[1]!) + .emit(stakingContract, 'RewardClaimed') + .withArgs(poolAddr, userB.address, local.claimableRewardForB); + await expect(txs[1]!) + .emit(stakingContract, 'SettledRewardUpdated') + .withArgs(poolAddr, userB.address, local.balanceB, 0, local.settledARps); + await expect(txs[1]!) + .emit(stakingContract, 'PendingRewardUpdated') + .withArgs(poolAddr, userB.address, 1275, lastCreditedB); local.claimRewardForA(); local.claimRewardForB(); await expectLocalCalculationRight(); diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index 183970cfd..593993a44 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -1,6 +1,6 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { expect } from 'chai'; -import { BigNumber, BigNumberish } from 'ethers'; +import { BigNumber, BigNumberish, ContractTransaction } from 'ethers'; import { ethers, network } from 'hardhat'; import { Staking, Staking__factory, TransparentUpgradeableProxy__factory } from '../../src/types'; @@ -132,12 +132,15 @@ describe('Staking test', () => { it('Should be able to propose validator with sufficient amount', async () => { for (let i = 1; i < validatorCandidates.length; i++) { const candidate = validatorCandidates[i]; - await stakingContract.connect(candidate).proposeValidator( + const tx = await stakingContract.connect(candidate).proposeValidator( candidate.address, candidate.address, 1, // 0.01% { value: minValidatorBalance } ); + await expect(tx) + .emit(stakingContract, 'ValidatorProposed') + .withArgs(candidate.address, candidate.address, i - 1); } poolAddr = validatorCandidates[1]; @@ -167,9 +170,13 @@ describe('Staking test', () => { }); it('Should be able to stake/unstake as a validator', async () => { - await stakingContract.connect(poolAddr).stake(poolAddr.address, { value: 1 }); + let tx: ContractTransaction; + tx = await stakingContract.connect(poolAddr).stake(poolAddr.address, { value: 1 }); + await expect(tx!).emit(stakingContract, 'Staked').withArgs(poolAddr.address, 1); expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance.add(1)); - await stakingContract.connect(poolAddr).unstake(poolAddr.address, 1); + + tx = await stakingContract.connect(poolAddr).unstake(poolAddr.address, 1); + await expect(tx!).emit(stakingContract, 'Unstaked').withArgs(poolAddr.address, 1); expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance); }); @@ -197,10 +204,16 @@ describe('Staking test', () => { }); it('Should be able to delegate/undelegate', async () => { - await stakingContract.connect(userA).delegate(otherPoolAddr.address, { value: 1 }); - await stakingContract.connect(userB).delegate(otherPoolAddr.address, { value: 1 }); + let tx: ContractTransaction; + tx = await stakingContract.connect(userA).delegate(otherPoolAddr.address, { value: 1 }); + await expect(tx!).emit(stakingContract, 'Delegated').withArgs(userA.address, otherPoolAddr.address, 1); + + tx = await stakingContract.connect(userB).delegate(otherPoolAddr.address, { value: 1 }); + await expect(tx!).emit(stakingContract, 'Delegated').withArgs(userB.address, otherPoolAddr.address, 1); expect(await stakingContract.totalBalance(otherPoolAddr.address)).eq(minValidatorBalance.add(2)); - await stakingContract.connect(userA).undelegate(otherPoolAddr.address, 1); + + tx = await stakingContract.connect(userA).undelegate(otherPoolAddr.address, 1); + await expect(tx!).emit(stakingContract, 'Undelegated').withArgs(userA.address, otherPoolAddr.address, 1); expect(await stakingContract.totalBalance(otherPoolAddr.address)).eq(minValidatorBalance.add(1)); }); }); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index 7c9333d8c..ab0ff071b 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -11,9 +11,9 @@ import { TransparentUpgradeableProxy__factory, MockSlashIndicator, MockSlashIndicator__factory, + StakingVesting__factory, } from '../../src/types'; -import * as RoninValidatorSet from '../../src/script/ronin-validator-set'; -import { StakingVesting__factory } from '../../src/types/factories/StakingVesting__factory'; +import * as RoninValidatorSet from '../helpers/ronin-validator-set'; let roninValidatorSet: MockRoninValidatorSetEpochSetter; let stakingContract: Staking; @@ -257,9 +257,9 @@ describe('Ronin Validator Set test', () => { it('Should not allocate reward for the slashed validator', async () => { let tx: ContractTransaction; const balance = await treasury.getBalance(); + await slashIndicator.slashMisdemeanor(coinbase.address); tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); await RoninValidatorSet.expects.emitRewardDeprecatedEvent(tx!, coinbase.address, 100); - await slashIndicator.slashMisdemeanor(coinbase.address); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); await roninValidatorSet.endPeriod(); From da51fda0dfdd0cb50018299af9b257742dfe7631 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Thu, 8 Sep 2022 18:44:16 +0700 Subject: [PATCH 100/190] [StashIndicator] Add governance functions --- contracts/interfaces/ISlashIndicator.sol | 25 ++++++++++++++++- contracts/mocks/MockSlashIndicator.sol | 6 +++++ contracts/slashing/SlashIndicator.sol | 34 ++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index b7626d006..d26fd94cf 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -59,10 +59,33 @@ interface ISlashIndicator { * * Requirements: * - Only governance admin can call this method - * */ function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external; + /** + * @dev Sets the slash felony amount + * + * Requirements: + * - Only governance admin can call this method + */ + function setSlashFelonyAmount(uint256 _slashFelonyAmount) external; + + /** + * @dev Sets the slash double sign amount + * + * Requirements: + * - Only governance admin can call this method + */ + function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external; + + /** + * @dev Sets the felony jail duration + * + * Requirements: + * - Only governance admin can call this method + */ + function setFelonyJailDuration(uint256 _felonyJailDuration) external; + /** * @dev Gets slash indicator of a validator. */ diff --git a/contracts/mocks/MockSlashIndicator.sol b/contracts/mocks/MockSlashIndicator.sol index c1410992d..0d36a3ad1 100644 --- a/contracts/mocks/MockSlashIndicator.sol +++ b/contracts/mocks/MockSlashIndicator.sol @@ -51,4 +51,10 @@ contract MockSlashIndicator is ISlashIndicator { function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external override {} function governanceAdmin() external view override returns (address) {} + + function setSlashFelonyAmount(uint256 _slashFelonyAmount) external override {} + + function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external override {} + + function setFelonyJailDuration(uint256 _felonyJailDuration) external override {} } diff --git a/contracts/slashing/SlashIndicator.sol b/contracts/slashing/SlashIndicator.sol index f28c92576..bd2702f6e 100644 --- a/contracts/slashing/SlashIndicator.sol +++ b/contracts/slashing/SlashIndicator.sol @@ -39,6 +39,11 @@ contract SlashIndicator is ISlashIndicator, Initializable { _; } + modifier onlyGovernanceAdmin() { + require(msg.sender == _governanceAdmin, "SlashIndicator: method caller is not the governance admin"); + _; + } + modifier oncePerBlock() { require( block.number > lastSlashedBlock, @@ -85,7 +90,7 @@ contract SlashIndicator is ISlashIndicator, Initializable { uint256 _count = ++_unavailabilityIndicator[_validatorAddr]; - // Slashs the validator as either the fenoly or the misdemeanor + // Slashes the validator as either the fenoly or the misdemeanor if (_count == felonyThreshold) { emit ValidatorSlashed(_validatorAddr, SlashType.FELONY); validatorContract.slash(_validatorAddr, block.number + felonyJailDuration, slashFelonyAmount); @@ -136,11 +141,36 @@ contract SlashIndicator is ISlashIndicator, Initializable { /** * @inheritdoc ISlashIndicator */ - function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external override { + function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) + external + override + onlyGovernanceAdmin + { felonyThreshold = _felonyThreshold; misdemeanorThreshold = _misdemeanorThreshold; } + /** + * @inheritdoc ISlashIndicator + */ + function setSlashFelonyAmount(uint256 _slashFelonyAmount) external override onlyGovernanceAdmin { + slashFelonyAmount = _slashFelonyAmount; + } + + /** + * @inheritdoc ISlashIndicator + */ + function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external override onlyGovernanceAdmin { + slashDoubleSignAmount = _slashDoubleSignAmount; + } + + /** + * @inheritdoc ISlashIndicator + */ + function setFelonyJailDuration(uint256 _felonyJailDuration) external override onlyGovernanceAdmin { + felonyJailDuration = _felonyJailDuration; + } + /////////////////////////////////////////////////////////////////////////////////////// // QUERY FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// From d71d4cf8273e75e8de7dd9acb0e5c475de777c9c Mon Sep 17 00:00:00 2001 From: nxqbao Date: Fri, 9 Sep 2022 12:42:51 +0700 Subject: [PATCH 101/190] [SlashIndicator] Add test and integration test --- test/slash/SlashIndicatorIntegration.test.ts | 219 +++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 test/slash/SlashIndicatorIntegration.test.ts diff --git a/test/slash/SlashIndicatorIntegration.test.ts b/test/slash/SlashIndicatorIntegration.test.ts new file mode 100644 index 000000000..fc6c4ea1f --- /dev/null +++ b/test/slash/SlashIndicatorIntegration.test.ts @@ -0,0 +1,219 @@ +import { expect } from 'chai'; +import { network, ethers, deployments } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; + +import { + SlashIndicator, + SlashIndicator__factory, + Staking, + Staking__factory, + RoninValidatorSet__factory, + MockRoninValidatorSetEpochSetter__factory, + MockRoninValidatorSetEpochSetter, + TransparentUpgradeableProxy__factory, +} from '../../src/types'; +import { Network, slashIndicatorConf, roninValidatorSetConf, stakingConfig, initAddress } from '../../src/config'; +import { BigNumber, ContractTransaction } from 'ethers'; +import { SlashType } from './slashType'; +import { expects as RoninValidatorSetExpects } from '../../src/script/ronin-validator-set'; + +let slashIndicatorContract: SlashIndicator; +let stakingContract: Staking; +let roninValidatorSetContract: MockRoninValidatorSetEpochSetter; + +let coinbase: SignerWithAddress; +let treasury: SignerWithAddress; +let deployer: SignerWithAddress; +let governanceAdmin: SignerWithAddress; +let proxyAdmin: SignerWithAddress; +let validatorCandidates: SignerWithAddress[]; + +const slashFelonyAmount = BigNumber.from(1); +const slashDoubleSignAmount = 1000; +const maxValidatorNumber = 4; +const minValidatorBalance = BigNumber.from(100); +const felonyJailDuration = 28800 * 2; +const misdemeanorThreshold = 10; +const felonyThreshold = 20; + +const mineBatchTxs = async (fn: () => Promise) => { + await network.provider.send('evm_setAutomine', [false]); + await fn(); + await network.provider.send('evm_mine'); + await network.provider.send('evm_setAutomine', [true]); +}; + +describe('Slash indicator integration test', () => { + before(async () => { + [coinbase, treasury, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + validatorCandidates = validatorCandidates.slice(0, 5); + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + + if (network.name == Network.Hardhat) { + initAddress[network.name] = { + governanceAdmin: governanceAdmin.address, + }; + slashIndicatorConf[network.name] = { + misdemeanorThreshold: misdemeanorThreshold, + felonyThreshold: felonyThreshold, + slashFelonyAmount: slashFelonyAmount, + slashDoubleSignAmount: slashDoubleSignAmount, + felonyJailBlocks: felonyJailDuration, + }; + roninValidatorSetConf[network.name] = { + maxValidatorNumber: 21, + numberOfBlocksInEpoch: 600, + numberOfEpochsInPeriod: 48, // 1 day + }; + stakingConfig[network.name] = { + minValidatorBalance: minValidatorBalance, + maxValidatorCandidate: maxValidatorNumber, + }; + } + + const nonce = await deployer.getTransactionCount(); + const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); + const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); + + const slashLogicContract = await new SlashIndicator__factory(deployer).deploy(); + await slashLogicContract.deployed(); + + const slashProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + slashLogicContract.address, + proxyAdmin.address, + slashLogicContract.interface.encodeFunctionData('initialize', [ + slashIndicatorConf[network.name]!.misdemeanorThreshold, + slashIndicatorConf[network.name]!.felonyThreshold, + roninValidatorSetAddr, + slashIndicatorConf[network.name]!.slashFelonyAmount, + slashIndicatorConf[network.name]!.slashDoubleSignAmount, + slashIndicatorConf[network.name]!.felonyJailBlocks, + ]) + ); + await slashProxyContract.deployed(); + slashIndicatorContract = SlashIndicator__factory.connect(slashProxyContract.address, deployer); + + roninValidatorSetContract = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy( + governanceAdmin.address, + slashIndicatorContract.address, + stakingContractAddr, + maxValidatorNumber + ); + await roninValidatorSetContract.deployed(); + + const stakingLogicContract = await new Staking__factory(deployer).deploy(); + await stakingLogicContract.deployed(); + + const stakingProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + stakingLogicContract.address, + proxyAdmin.address, + stakingLogicContract.interface.encodeFunctionData('initialize', [ + roninValidatorSetContract.address, + governanceAdmin.address, + 100, + minValidatorBalance, + ]) + ); + await stakingProxyContract.deployed(); + stakingContract = Staking__factory.connect(stakingProxyContract.address, deployer); + + expect(roninValidatorSetAddr.toLowerCase()).eq(roninValidatorSetContract.address.toLowerCase()); + expect(stakingContractAddr.toLowerCase()).eq(stakingContract.address.toLowerCase()); + }); + + describe('Integrate with Validator Set', async () => { + describe('Slash indicator', async () => { + describe('Configuration test', async () => { + it('Should configs the roninValidatorSetContract correctly', async () => { + let _validatorContract = await slashIndicatorContract.validatorContract(); + expect(_validatorContract).to.eq(roninValidatorSetContract.address); + }); + }); + + describe('Interact to Validator Set in slashing function', async () => { + describe('Slash misdemeanor validator', async () => { + let slasheeIdx = 1; + + it('Should the ValidatorSet contract emit event', async () => { + for (let i = 0; i < misdemeanorThreshold - 1; i++) { + await slashIndicatorContract.connect(coinbase).slash(validatorCandidates[slasheeIdx].address); + } + let tx = slashIndicatorContract.connect(coinbase).slash(validatorCandidates[slasheeIdx].address); + + await expect(tx) + .to.emit(slashIndicatorContract, 'ValidatorSlashed') + .withArgs(validatorCandidates[slasheeIdx].address, SlashType.MISDEMEANOR); + + await expect(tx) + .to.emit(roninValidatorSetContract, 'ValidatorSlashed') + .withArgs(validatorCandidates[slasheeIdx].address, 0, 0); + }); + }); + + describe.skip('Slash felony validator', async () => { + let slasheeIdx = 2; + let updateValidatorTx: ContractTransaction; + let slashValidatorTx: ContractTransaction; + + before(async () => { + await stakingContract + .connect(validatorCandidates[slasheeIdx]) + .proposeValidator( + validatorCandidates[slasheeIdx].address, + validatorCandidates[slasheeIdx].address, + 2_00, + { + value: minValidatorBalance, + } + ); + + await mineBatchTxs(async () => { + await roninValidatorSetContract.connect(coinbase).endEpoch(); + updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + }); + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, [ + validatorCandidates[slasheeIdx].address, + ]); + + expect(await roninValidatorSetContract.getValidators()).have.same.members([ + validatorCandidates[slasheeIdx].address, + ]); + }); + + it('Should the ValidatorSet contract emit event', async () => { + for (let i = 0; i < felonyThreshold - 1; i++) { + await slashIndicatorContract.connect(coinbase).slash(validatorCandidates[slasheeIdx].address); + } + slashValidatorTx = await slashIndicatorContract + .connect(coinbase) + .slash(validatorCandidates[slasheeIdx].address); + + await expect(slashValidatorTx) + .to.emit(slashIndicatorContract, 'ValidatorSlashed') + .withArgs(validatorCandidates[slasheeIdx].address, SlashType.FELONY); + + let blockNumber = await network.provider.send('eth_blockNumber'); + + await expect(slashValidatorTx) + .to.emit(roninValidatorSetContract, 'ValidatorSlashed') + .withArgs( + validatorCandidates[slasheeIdx].address, + BigNumber.from(blockNumber).add(felonyJailDuration), + slashFelonyAmount + ); + }); + + it('Should the validator get subtracted staked amount', async () => {}); + + it('Should the validator is put in jail', async () => {}); + + it('Should the validator set exclude the jailed validator in the next epoch', async () => {}); + + it('Should the validator candidate cannot join as a validator when jail time is over, due to insufficient fund', async () => {}); + + it('Should the validator top-up balance and be able to re-join the validator set', async () => {}); + }); + }); + }); + }); +}); From 3b6d5014d4e8701cecb34f5bf8e201e77b4533ad Mon Sep 17 00:00:00 2001 From: nxqbao Date: Fri, 9 Sep 2022 14:24:56 +0700 Subject: [PATCH 102/190] chore: add TODO for contracts --- contracts/interfaces/IStaking.sol | 2 +- contracts/ronin-validator/RoninValidatorSet.sol | 1 + contracts/staking/StakingManager.sol | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 37527fd84..7b308ab01 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -99,7 +99,7 @@ interface IStaking is IRewardPool { function setMaxValidatorCandidate(uint256) external; /////////////////////////////////////////////////////////////////////////////////////// - // FUNCTIONS FOR VALIDATOR // + // FUNCTIONS FOR VALIDATOR CONTRACT // /////////////////////////////////////////////////////////////////////////////////////// /** diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index eb4cd48d2..73ab1ac35 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -359,6 +359,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { */ function _updateValidatorSet() internal { // TODO: measure gas metrics from getting candidates + // TODO: filter validators that do not have enough min balance (address[] memory _candidates, uint256[] memory _weights) = IStaking(_stakingContract).getCandidateWeights(); uint256 _newLength = _candidates.length; for (uint256 _i; _i < _candidates.length; _i++) { diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index f1c2ebeb7..32ca36798 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -174,6 +174,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { require(_candidate.candidateAdmin == _user, "StakingManager: user is not the candidate admin"); require(_amount <= _candidate.stakedAmount, "StakingManager: insufficient staked amount"); + // TODO: exclude min balance check when staked is deducted by slashing uint256 remainAmount = _candidate.stakedAmount - _amount; require(remainAmount >= minValidatorBalance(), "StakingManager: invalid staked amount left"); From 3006dd73c226c787e975a73029da627a3c574c2a Mon Sep 17 00:00:00 2001 From: nxqbao Date: Fri, 9 Sep 2022 14:25:53 +0700 Subject: [PATCH 103/190] refactor: refactor test --- ...inValidatorSetEpochSetterAndQueryInfo.sol} | 9 +- test/integration/ActionSlashValidators.ts | 221 ++++++++++++++++++ test/slash/SlashIndicatorIntegration.test.ts | 219 ----------------- test/utils.ts | 8 + test/validator/RoninValidatorSet.test.ts | 18 +- 5 files changed, 243 insertions(+), 232 deletions(-) rename contracts/mocks/{MockRoninValidatorSetEpochSetter.sol => MockRoninValidatorSetEpochSetterAndQueryInfo.sol} (82%) create mode 100644 test/integration/ActionSlashValidators.ts delete mode 100644 test/slash/SlashIndicatorIntegration.test.ts create mode 100644 test/utils.ts diff --git a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol b/contracts/mocks/MockRoninValidatorSetEpochSetterAndQueryInfo.sol similarity index 82% rename from contracts/mocks/MockRoninValidatorSetEpochSetter.sol rename to contracts/mocks/MockRoninValidatorSetEpochSetterAndQueryInfo.sol index 5eb7e4cc3..258305ba8 100644 --- a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol +++ b/contracts/mocks/MockRoninValidatorSetEpochSetterAndQueryInfo.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.9; import "../ronin-validator/RoninValidatorSet.sol"; -contract MockRoninValidatorSetEpochSetter is RoninValidatorSet { +contract MockRoninValidatorSetEpochSetterAndQueryInfo is RoninValidatorSet { uint256[] internal _epochs; uint256[] internal _periods; @@ -63,4 +63,11 @@ contract MockRoninValidatorSetEpochSetter is RoninValidatorSet { } return false; } + + function getJailUntils(address[] calldata _addrs) public view returns (uint256[] memory jailUntils_) { + jailUntils_ = new uint256[](_addrs.length); + for (uint _i = 0; _i < _addrs.length; _i++) { + jailUntils_[_i] = _jailedUntil[_addrs[_i]]; + } + } } diff --git a/test/integration/ActionSlashValidators.ts b/test/integration/ActionSlashValidators.ts new file mode 100644 index 000000000..b1bbf8075 --- /dev/null +++ b/test/integration/ActionSlashValidators.ts @@ -0,0 +1,221 @@ +import { expect } from 'chai'; +import { network, ethers, deployments } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; + +import { + SlashIndicator, + SlashIndicator__factory, + Staking, + Staking__factory, + RoninValidatorSet__factory, + MockRoninValidatorSetEpochSetterAndQueryInfo__factory, + MockRoninValidatorSetEpochSetterAndQueryInfo, + TransparentUpgradeableProxy__factory, +} from '../../src/types'; +import { Network, slashIndicatorConf, roninValidatorSetConf, stakingConfig, initAddress } from '../../src/config'; +import { BigNumber, ContractTransaction } from 'ethers'; +import { SlashType } from '../slash/slashType'; +import { expects as RoninValidatorSetExpects } from '../../src/script/ronin-validator-set'; +import { mineBatchTxs } from '../utils'; + +let slashIndicatorContract: SlashIndicator; +let stakingContract: Staking; +let roninValidatorSetContract: MockRoninValidatorSetEpochSetterAndQueryInfo; + +let coinbase: SignerWithAddress; +let treasury: SignerWithAddress; +let deployer: SignerWithAddress; +let governanceAdmin: SignerWithAddress; +let proxyAdmin: SignerWithAddress; +let validatorCandidates: SignerWithAddress[]; + +const slashFelonyAmount = BigNumber.from(1); +const slashDoubleSignAmount = 1000; +const maxValidatorNumber = 4; +const minValidatorBalance = BigNumber.from(100); +const felonyJailDuration = 28800 * 2; +const misdemeanorThreshold = 10; +const felonyThreshold = 20; + +describe('[Integration] Slash validators', () => { + before(async () => { + [coinbase, treasury, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + validatorCandidates = validatorCandidates.slice(0, 5); + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + + if (network.name == Network.Hardhat) { + initAddress[network.name] = { + governanceAdmin: governanceAdmin.address, + }; + slashIndicatorConf[network.name] = { + misdemeanorThreshold: misdemeanorThreshold, + felonyThreshold: felonyThreshold, + slashFelonyAmount: slashFelonyAmount, + slashDoubleSignAmount: slashDoubleSignAmount, + felonyJailBlocks: felonyJailDuration, + }; + roninValidatorSetConf[network.name] = { + maxValidatorNumber: 21, + numberOfBlocksInEpoch: 600, + numberOfEpochsInPeriod: 48, // 1 day + }; + stakingConfig[network.name] = { + minValidatorBalance: minValidatorBalance, + maxValidatorCandidate: maxValidatorNumber, + }; + } + + const nonce = await deployer.getTransactionCount(); + const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); + const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); + + const slashLogicContract = await new SlashIndicator__factory(deployer).deploy(); + await slashLogicContract.deployed(); + + const slashProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + slashLogicContract.address, + proxyAdmin.address, + slashLogicContract.interface.encodeFunctionData('initialize', [ + slashIndicatorConf[network.name]!.misdemeanorThreshold, + slashIndicatorConf[network.name]!.felonyThreshold, + roninValidatorSetAddr, + slashIndicatorConf[network.name]!.slashFelonyAmount, + slashIndicatorConf[network.name]!.slashDoubleSignAmount, + slashIndicatorConf[network.name]!.felonyJailBlocks, + ]) + ); + await slashProxyContract.deployed(); + slashIndicatorContract = SlashIndicator__factory.connect(slashProxyContract.address, deployer); + + roninValidatorSetContract = await new MockRoninValidatorSetEpochSetterAndQueryInfo__factory(deployer).deploy( + governanceAdmin.address, + slashIndicatorContract.address, + stakingContractAddr, + maxValidatorNumber + ); + await roninValidatorSetContract.deployed(); + + const stakingLogicContract = await new Staking__factory(deployer).deploy(); + await stakingLogicContract.deployed(); + + const stakingProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + stakingLogicContract.address, + proxyAdmin.address, + stakingLogicContract.interface.encodeFunctionData('initialize', [ + roninValidatorSetContract.address, + governanceAdmin.address, + 100, + minValidatorBalance, + ]) + ); + await stakingProxyContract.deployed(); + stakingContract = Staking__factory.connect(stakingProxyContract.address, deployer); + + expect(roninValidatorSetAddr.toLowerCase()).eq(roninValidatorSetContract.address.toLowerCase()); + expect(stakingContractAddr.toLowerCase()).eq(stakingContract.address.toLowerCase()); + }); + + describe('Configuration test', async () => { + it('Should the SlashContract config the ValidatorSetContract correctly', async () => { + let _validatorContract = await slashIndicatorContract.validatorContract(); + expect(_validatorContract).to.eq(roninValidatorSetContract.address); + }); + + it('Should the ValidatorSetContract config the SlashContract correctly', async () => { + let _slashContract = await roninValidatorSetContract.slashIndicatorContract(); + expect(_slashContract).to.eq(slashIndicatorContract.address); + }); + + it('Should the ValidatorSetContract config the StakingContract correctly', async () => { + let _stakingContract = await roninValidatorSetContract.stakingContract(); + expect(_stakingContract).to.eq(stakingContract.address); + }); + }); + + describe('Slash on one validator', async () => { + describe('Interact to Validator Set in slashing function', async () => { + describe('Slash misdemeanor validator', async () => { + let slasheeIdx = 1; + + it('Should the ValidatorSet contract emit event', async () => { + for (let i = 0; i < misdemeanorThreshold - 1; i++) { + await slashIndicatorContract.connect(coinbase).slash(validatorCandidates[slasheeIdx].address); + } + let tx = slashIndicatorContract.connect(coinbase).slash(validatorCandidates[slasheeIdx].address); + + await expect(tx) + .to.emit(slashIndicatorContract, 'ValidatorSlashed') + .withArgs(validatorCandidates[slasheeIdx].address, SlashType.MISDEMEANOR); + + await expect(tx) + .to.emit(roninValidatorSetContract, 'ValidatorSlashed') + .withArgs(validatorCandidates[slasheeIdx].address, 0, 0); + }); + }); + + describe("Slash felony validator -- when the validator' balance is sufficient after being slashed", async () => { + let slasheeIdx = 2; + let updateValidatorTx: ContractTransaction; + let slashValidatorTx: ContractTransaction; + + before(async () => { + await stakingContract + .connect(validatorCandidates[slasheeIdx]) + .proposeValidator(validatorCandidates[slasheeIdx].address, validatorCandidates[slasheeIdx].address, 2_00, { + value: minValidatorBalance.add(slashFelonyAmount.mul(10)), + }); + + await mineBatchTxs(async () => { + await roninValidatorSetContract.connect(coinbase).endEpoch(); + updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + }); + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, [ + validatorCandidates[slasheeIdx].address, + ]); + + expect(await roninValidatorSetContract.getValidators()).eql([validatorCandidates[slasheeIdx].address]); + }); + + it('Should the ValidatorSet contract emit event', async () => { + for (let i = 0; i < felonyThreshold - 1; i++) { + await slashIndicatorContract.connect(coinbase).slash(validatorCandidates[slasheeIdx].address); + } + slashValidatorTx = await slashIndicatorContract + .connect(coinbase) + .slash(validatorCandidates[slasheeIdx].address); + + await expect(slashValidatorTx) + .to.emit(slashIndicatorContract, 'ValidatorSlashed') + .withArgs(validatorCandidates[slasheeIdx].address, SlashType.FELONY); + + let blockNumber = await network.provider.send('eth_blockNumber'); + + await expect(slashValidatorTx) + .to.emit(roninValidatorSetContract, 'ValidatorSlashed') + .withArgs( + validatorCandidates[slasheeIdx].address, + BigNumber.from(blockNumber).add(felonyJailDuration), + slashFelonyAmount + ); + }); + + it('Should the validator is put in jail', async () => { + let blockNumber = await network.provider.send('eth_blockNumber'); + expect(await roninValidatorSetContract.getJailUntils([validatorCandidates[slasheeIdx].address])).eql([ + BigNumber.from(blockNumber).add(felonyJailDuration), + ]); + }); + + it('Should the validator get subtracted staked amount', async () => {}); + + it('Should the validator set exclude the jailed validator in the next epoch', async () => {}); + + it('Should the validator candidate cannot join as a validator when jail time is over, due to insufficient fund', async () => {}); + + it('Should the validator top-up balance and be able to re-join the validator set', async () => {}); + }); + + describe.skip("Slash felony validator -- when the validator's balance is equal to minimum balance", async () => {}); + }); + }); +}); diff --git a/test/slash/SlashIndicatorIntegration.test.ts b/test/slash/SlashIndicatorIntegration.test.ts deleted file mode 100644 index fc6c4ea1f..000000000 --- a/test/slash/SlashIndicatorIntegration.test.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { expect } from 'chai'; -import { network, ethers, deployments } from 'hardhat'; -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; - -import { - SlashIndicator, - SlashIndicator__factory, - Staking, - Staking__factory, - RoninValidatorSet__factory, - MockRoninValidatorSetEpochSetter__factory, - MockRoninValidatorSetEpochSetter, - TransparentUpgradeableProxy__factory, -} from '../../src/types'; -import { Network, slashIndicatorConf, roninValidatorSetConf, stakingConfig, initAddress } from '../../src/config'; -import { BigNumber, ContractTransaction } from 'ethers'; -import { SlashType } from './slashType'; -import { expects as RoninValidatorSetExpects } from '../../src/script/ronin-validator-set'; - -let slashIndicatorContract: SlashIndicator; -let stakingContract: Staking; -let roninValidatorSetContract: MockRoninValidatorSetEpochSetter; - -let coinbase: SignerWithAddress; -let treasury: SignerWithAddress; -let deployer: SignerWithAddress; -let governanceAdmin: SignerWithAddress; -let proxyAdmin: SignerWithAddress; -let validatorCandidates: SignerWithAddress[]; - -const slashFelonyAmount = BigNumber.from(1); -const slashDoubleSignAmount = 1000; -const maxValidatorNumber = 4; -const minValidatorBalance = BigNumber.from(100); -const felonyJailDuration = 28800 * 2; -const misdemeanorThreshold = 10; -const felonyThreshold = 20; - -const mineBatchTxs = async (fn: () => Promise) => { - await network.provider.send('evm_setAutomine', [false]); - await fn(); - await network.provider.send('evm_mine'); - await network.provider.send('evm_setAutomine', [true]); -}; - -describe('Slash indicator integration test', () => { - before(async () => { - [coinbase, treasury, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); - validatorCandidates = validatorCandidates.slice(0, 5); - await network.provider.send('hardhat_setCoinbase', [coinbase.address]); - - if (network.name == Network.Hardhat) { - initAddress[network.name] = { - governanceAdmin: governanceAdmin.address, - }; - slashIndicatorConf[network.name] = { - misdemeanorThreshold: misdemeanorThreshold, - felonyThreshold: felonyThreshold, - slashFelonyAmount: slashFelonyAmount, - slashDoubleSignAmount: slashDoubleSignAmount, - felonyJailBlocks: felonyJailDuration, - }; - roninValidatorSetConf[network.name] = { - maxValidatorNumber: 21, - numberOfBlocksInEpoch: 600, - numberOfEpochsInPeriod: 48, // 1 day - }; - stakingConfig[network.name] = { - minValidatorBalance: minValidatorBalance, - maxValidatorCandidate: maxValidatorNumber, - }; - } - - const nonce = await deployer.getTransactionCount(); - const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); - const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); - - const slashLogicContract = await new SlashIndicator__factory(deployer).deploy(); - await slashLogicContract.deployed(); - - const slashProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( - slashLogicContract.address, - proxyAdmin.address, - slashLogicContract.interface.encodeFunctionData('initialize', [ - slashIndicatorConf[network.name]!.misdemeanorThreshold, - slashIndicatorConf[network.name]!.felonyThreshold, - roninValidatorSetAddr, - slashIndicatorConf[network.name]!.slashFelonyAmount, - slashIndicatorConf[network.name]!.slashDoubleSignAmount, - slashIndicatorConf[network.name]!.felonyJailBlocks, - ]) - ); - await slashProxyContract.deployed(); - slashIndicatorContract = SlashIndicator__factory.connect(slashProxyContract.address, deployer); - - roninValidatorSetContract = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy( - governanceAdmin.address, - slashIndicatorContract.address, - stakingContractAddr, - maxValidatorNumber - ); - await roninValidatorSetContract.deployed(); - - const stakingLogicContract = await new Staking__factory(deployer).deploy(); - await stakingLogicContract.deployed(); - - const stakingProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( - stakingLogicContract.address, - proxyAdmin.address, - stakingLogicContract.interface.encodeFunctionData('initialize', [ - roninValidatorSetContract.address, - governanceAdmin.address, - 100, - minValidatorBalance, - ]) - ); - await stakingProxyContract.deployed(); - stakingContract = Staking__factory.connect(stakingProxyContract.address, deployer); - - expect(roninValidatorSetAddr.toLowerCase()).eq(roninValidatorSetContract.address.toLowerCase()); - expect(stakingContractAddr.toLowerCase()).eq(stakingContract.address.toLowerCase()); - }); - - describe('Integrate with Validator Set', async () => { - describe('Slash indicator', async () => { - describe('Configuration test', async () => { - it('Should configs the roninValidatorSetContract correctly', async () => { - let _validatorContract = await slashIndicatorContract.validatorContract(); - expect(_validatorContract).to.eq(roninValidatorSetContract.address); - }); - }); - - describe('Interact to Validator Set in slashing function', async () => { - describe('Slash misdemeanor validator', async () => { - let slasheeIdx = 1; - - it('Should the ValidatorSet contract emit event', async () => { - for (let i = 0; i < misdemeanorThreshold - 1; i++) { - await slashIndicatorContract.connect(coinbase).slash(validatorCandidates[slasheeIdx].address); - } - let tx = slashIndicatorContract.connect(coinbase).slash(validatorCandidates[slasheeIdx].address); - - await expect(tx) - .to.emit(slashIndicatorContract, 'ValidatorSlashed') - .withArgs(validatorCandidates[slasheeIdx].address, SlashType.MISDEMEANOR); - - await expect(tx) - .to.emit(roninValidatorSetContract, 'ValidatorSlashed') - .withArgs(validatorCandidates[slasheeIdx].address, 0, 0); - }); - }); - - describe.skip('Slash felony validator', async () => { - let slasheeIdx = 2; - let updateValidatorTx: ContractTransaction; - let slashValidatorTx: ContractTransaction; - - before(async () => { - await stakingContract - .connect(validatorCandidates[slasheeIdx]) - .proposeValidator( - validatorCandidates[slasheeIdx].address, - validatorCandidates[slasheeIdx].address, - 2_00, - { - value: minValidatorBalance, - } - ); - - await mineBatchTxs(async () => { - await roninValidatorSetContract.connect(coinbase).endEpoch(); - updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); - }); - await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, [ - validatorCandidates[slasheeIdx].address, - ]); - - expect(await roninValidatorSetContract.getValidators()).have.same.members([ - validatorCandidates[slasheeIdx].address, - ]); - }); - - it('Should the ValidatorSet contract emit event', async () => { - for (let i = 0; i < felonyThreshold - 1; i++) { - await slashIndicatorContract.connect(coinbase).slash(validatorCandidates[slasheeIdx].address); - } - slashValidatorTx = await slashIndicatorContract - .connect(coinbase) - .slash(validatorCandidates[slasheeIdx].address); - - await expect(slashValidatorTx) - .to.emit(slashIndicatorContract, 'ValidatorSlashed') - .withArgs(validatorCandidates[slasheeIdx].address, SlashType.FELONY); - - let blockNumber = await network.provider.send('eth_blockNumber'); - - await expect(slashValidatorTx) - .to.emit(roninValidatorSetContract, 'ValidatorSlashed') - .withArgs( - validatorCandidates[slasheeIdx].address, - BigNumber.from(blockNumber).add(felonyJailDuration), - slashFelonyAmount - ); - }); - - it('Should the validator get subtracted staked amount', async () => {}); - - it('Should the validator is put in jail', async () => {}); - - it('Should the validator set exclude the jailed validator in the next epoch', async () => {}); - - it('Should the validator candidate cannot join as a validator when jail time is over, due to insufficient fund', async () => {}); - - it('Should the validator top-up balance and be able to re-join the validator set', async () => {}); - }); - }); - }); - }); -}); diff --git a/test/utils.ts b/test/utils.ts new file mode 100644 index 000000000..286e93b14 --- /dev/null +++ b/test/utils.ts @@ -0,0 +1,8 @@ +import { network } from "hardhat"; + +export const mineBatchTxs = async (fn: () => Promise) => { + await network.provider.send('evm_setAutomine', [false]); + await fn(); + await network.provider.send('evm_mine'); + await network.provider.send('evm_setAutomine', [true]); +}; diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index ab0ff071b..75202b52a 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -5,17 +5,18 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { Staking, - MockRoninValidatorSetEpochSetter, - MockRoninValidatorSetEpochSetter__factory, + MockRoninValidatorSetEpochSetterAndQueryInfo, + MockRoninValidatorSetEpochSetterAndQueryInfo__factory, Staking__factory, TransparentUpgradeableProxy__factory, MockSlashIndicator, MockSlashIndicator__factory, StakingVesting__factory, } from '../../src/types'; -import * as RoninValidatorSet from '../helpers/ronin-validator-set'; +import * as RoninValidatorSet from '../../src/script/ronin-validator-set'; +import { mineBatchTxs } from '../utils'; -let roninValidatorSet: MockRoninValidatorSetEpochSetter; +let roninValidatorSet: MockRoninValidatorSetEpochSetterAndQueryInfo; let stakingContract: Staking; let slashIndicator: MockSlashIndicator; @@ -33,13 +34,6 @@ const slashDoubleSignAmount = 1000; const maxValidatorNumber = 4; const minValidatorBalance = BigNumber.from(2); -const mineBatchTxs = async (fn: () => Promise) => { - await network.provider.send('evm_setAutomine', [false]); - await fn(); - await network.provider.send('evm_mine'); - await network.provider.send('evm_setAutomine', [true]); -}; - describe('Ronin Validator Set test', () => { before(async () => { [coinbase, treasury, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); @@ -64,7 +58,7 @@ describe('Ronin Validator Set test', () => { ); await slashIndicator.deployed(); - roninValidatorSet = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy( + roninValidatorSet = await new MockRoninValidatorSetEpochSetterAndQueryInfo__factory(deployer).deploy( governanceAdmin.address, slashIndicator.address, stakingContractAddr, From 35e9d954ccd15673f867115c929a1b416fbad838 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Fri, 9 Sep 2022 14:26:49 +0700 Subject: [PATCH 104/190] [RoninValidatorSet] Change expect `.have.same.members` to `.eql` --- test/validator/RoninValidatorSet.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index 75202b52a..4b34fb7c4 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -110,7 +110,7 @@ describe('Ronin Validator Set test', () => { tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, []); - expect(await roninValidatorSet.getValidators()).have.same.members([]); + expect(await roninValidatorSet.getValidators()).eql([]); }); it('Should be able to wrap up epoch and sync validator set from staking contract', async () => { @@ -135,7 +135,7 @@ describe('Ronin Validator Set test', () => { .map((_) => _.address) ); - expect(await roninValidatorSet.getValidators()).have.same.members( + expect(await roninValidatorSet.getValidators()).eql( validatorCandidates .slice(0, 4) .reverse() @@ -169,7 +169,7 @@ describe('Ronin Validator Set test', () => { ]; }); await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); - expect(await roninValidatorSet.getValidators()).have.same.members(currentValidatorSet); + expect(await roninValidatorSet.getValidators()).eql(currentValidatorSet); }); it('Should not be able to submit block reward using unauthorized account', async () => { From c0141a79d2e516a917302a72411974ecbe256091 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Fri, 9 Sep 2022 16:32:05 +0700 Subject: [PATCH 105/190] [Staking] Fix deductStaking function --- contracts/staking/Staking.sol | 1 - contracts/staking/StakingManager.sol | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 69028d890..802a2cab6 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -162,7 +162,6 @@ contract Staking is IStaking, StakingManager, Initializable { function deductStakingAmount(address _consensusAddr, uint256 _amount) external onlyValidatorContract { ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); _unstake(_consensusAddr, _candidate.candidateAdmin, _amount); - _undelegate(_consensusAddr, _candidate.candidateAdmin, _amount); } /** diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 32ca36798..37dc28a03 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -298,7 +298,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { address _poolAddr, address _user, uint256 _amount - ) internal { + ) private { require(_delegatedAmount[_poolAddr][_user] >= _amount, "StakingManager: insufficient amount to undelegate"); uint256 _newBalance = _delegatedAmount[_poolAddr][_user] - _amount; From 1fbffbae00918a0ad8ae01396dc52680ee1ff89a Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 12 Sep 2022 12:17:42 +0700 Subject: [PATCH 106/190] [ValidatorSet] Temporarily fix unstake on slashing felony --- contracts/ronin-validator/RoninValidatorSet.sol | 2 ++ contracts/staking/Staking.sol | 2 +- contracts/staking/StakingManager.sol | 10 +++++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index 73ab1ac35..afc0f71e6 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -359,7 +359,9 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { */ function _updateValidatorSet() internal { // TODO: measure gas metrics from getting candidates + // TODO: filter validators that do not have enough min balance + (address[] memory _candidates, uint256[] memory _weights) = IStaking(_stakingContract).getCandidateWeights(); uint256 _newLength = _candidates.length; for (uint256 _i; _i < _candidates.length; _i++) { diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 802a2cab6..9bbfc546e 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -161,7 +161,7 @@ contract Staking is IStaking, StakingManager, Initializable { */ function deductStakingAmount(address _consensusAddr, uint256 _amount) external onlyValidatorContract { ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); - _unstake(_consensusAddr, _candidate.candidateAdmin, _amount); + _unstake(_consensusAddr, _candidate.candidateAdmin, _amount, false); } /** diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 37dc28a03..daa3ec189 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -85,7 +85,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { */ function unstake(address _consensusAddr, uint256 _amount) external override { address _delegator = msg.sender; - _unstake(_consensusAddr, _delegator, _amount); + _unstake(_consensusAddr, _delegator, _amount, true); // TODO(Thor): replace by `call` and use reentrancy gruard require(payable(msg.sender).send(_amount), "StakingManager: could not transfer RON"); } @@ -96,6 +96,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { function renounce( address /* _consensusAddr */ ) external { + // TODO(Thor): implement this function revert("unimplemented"); } @@ -168,7 +169,8 @@ abstract contract StakingManager is IStaking, RewardCalculation { function _unstake( address _poolAddr, address _user, - uint256 _amount + uint256 _amount, + bool _checkMinBalance ) internal { ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); require(_candidate.candidateAdmin == _user, "StakingManager: user is not the candidate admin"); @@ -176,7 +178,9 @@ abstract contract StakingManager is IStaking, RewardCalculation { // TODO: exclude min balance check when staked is deducted by slashing uint256 remainAmount = _candidate.stakedAmount - _amount; - require(remainAmount >= minValidatorBalance(), "StakingManager: invalid staked amount left"); + if (_checkMinBalance) { + require(remainAmount >= minValidatorBalance(), "StakingManager: invalid staked amount left"); + } _candidate.stakedAmount -= _amount; emit Unstaked(_poolAddr, _amount); From 516ff7b8d365c465c4e465b4de635a74c8711784 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 12 Sep 2022 12:20:39 +0700 Subject: [PATCH 107/190] [Slash] Add integration test --- test/integration/ActionSlashValidators.ts | 305 +++++++++++++++++----- 1 file changed, 238 insertions(+), 67 deletions(-) diff --git a/test/integration/ActionSlashValidators.ts b/test/integration/ActionSlashValidators.ts index b1bbf8075..3f3d2d148 100644 --- a/test/integration/ActionSlashValidators.ts +++ b/test/integration/ActionSlashValidators.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { network, ethers, deployments } from 'hardhat'; +import { network, ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { @@ -7,7 +7,6 @@ import { SlashIndicator__factory, Staking, Staking__factory, - RoninValidatorSet__factory, MockRoninValidatorSetEpochSetterAndQueryInfo__factory, MockRoninValidatorSetEpochSetterAndQueryInfo, TransparentUpgradeableProxy__factory, @@ -17,13 +16,13 @@ import { BigNumber, ContractTransaction } from 'ethers'; import { SlashType } from '../slash/slashType'; import { expects as RoninValidatorSetExpects } from '../../src/script/ronin-validator-set'; import { mineBatchTxs } from '../utils'; +import { Address } from 'hardhat-deploy/dist/types'; let slashIndicatorContract: SlashIndicator; let stakingContract: Staking; let roninValidatorSetContract: MockRoninValidatorSetEpochSetterAndQueryInfo; let coinbase: SignerWithAddress; -let treasury: SignerWithAddress; let deployer: SignerWithAddress; let governanceAdmin: SignerWithAddress; let proxyAdmin: SignerWithAddress; @@ -39,7 +38,7 @@ const felonyThreshold = 20; describe('[Integration] Slash validators', () => { before(async () => { - [coinbase, treasury, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + [coinbase, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); validatorCandidates = validatorCandidates.slice(0, 5); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); @@ -132,90 +131,262 @@ describe('[Integration] Slash validators', () => { }); }); - describe('Slash on one validator', async () => { - describe('Interact to Validator Set in slashing function', async () => { - describe('Slash misdemeanor validator', async () => { + describe('Slash one validator', async () => { + let expectingValidatorSet: Address[] = []; + + describe('Slash misdemeanor validator', async () => { + it('Should the ValidatorSet contract emit event', async () => { let slasheeIdx = 1; + let slashee = validatorCandidates[slasheeIdx]; + + for (let i = 0; i < misdemeanorThreshold - 1; i++) { + await slashIndicatorContract.connect(coinbase).slash(slashee.address); + } + let tx = slashIndicatorContract.connect(coinbase).slash(slashee.address); + + await expect(tx) + .to.emit(slashIndicatorContract, 'ValidatorSlashed') + .withArgs(slashee.address, SlashType.MISDEMEANOR); + + await expect(tx).to.emit(roninValidatorSetContract, 'ValidatorSlashed').withArgs(slashee.address, 0, 0); + }); + }); - it('Should the ValidatorSet contract emit event', async () => { - for (let i = 0; i < misdemeanorThreshold - 1; i++) { - await slashIndicatorContract.connect(coinbase).slash(validatorCandidates[slasheeIdx].address); - } - let tx = slashIndicatorContract.connect(coinbase).slash(validatorCandidates[slasheeIdx].address); + describe('Slash felony validator -- when the validators balance is sufficient after being slashed', async () => { + let updateValidatorTx: ContractTransaction; + let slashValidatorTx: ContractTransaction; + let slasheeIdx: number; + let slashee: SignerWithAddress; + let slasheeInitStakingAmount: BigNumber; + + before(async () => { + slasheeIdx = 2; + slashee = validatorCandidates[slasheeIdx]; + slasheeInitStakingAmount = minValidatorBalance.add(slashFelonyAmount.mul(10)); + await stakingContract.connect(slashee).proposeValidator(slashee.address, slashee.address, 2_00, { + value: slasheeInitStakingAmount, + }); - await expect(tx) - .to.emit(slashIndicatorContract, 'ValidatorSlashed') - .withArgs(validatorCandidates[slasheeIdx].address, SlashType.MISDEMEANOR); + expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(slasheeInitStakingAmount); - await expect(tx) - .to.emit(roninValidatorSetContract, 'ValidatorSlashed') - .withArgs(validatorCandidates[slasheeIdx].address, 0, 0); + await mineBatchTxs(async () => { + await roninValidatorSetContract.connect(coinbase).endEpoch(); + updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); }); + + expectingValidatorSet.push(slashee.address); + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); + + expect(await roninValidatorSetContract.getValidators()).eql(expectingValidatorSet); + }); + + it('Should the ValidatorSet contract emit event', async () => { + for (let i = 0; i < felonyThreshold - 1; i++) { + await slashIndicatorContract.connect(coinbase).slash(slashee.address); + } + slashValidatorTx = await slashIndicatorContract.connect(coinbase).slash(slashee.address); + + await expect(slashValidatorTx) + .to.emit(slashIndicatorContract, 'ValidatorSlashed') + .withArgs(slashee.address, SlashType.FELONY); + + let blockNumber = await network.provider.send('eth_blockNumber'); + + await expect(slashValidatorTx) + .to.emit(roninValidatorSetContract, 'ValidatorSlashed') + .withArgs(slashee.address, BigNumber.from(blockNumber).add(felonyJailDuration), slashFelonyAmount); + }); + + it('Should the validator is put in jail', async () => { + let blockNumber = await network.provider.send('eth_blockNumber'); + expect(await roninValidatorSetContract.getJailUntils(expectingValidatorSet)).eql([ + BigNumber.from(blockNumber).add(felonyJailDuration), + ]); }); - describe("Slash felony validator -- when the validator' balance is sufficient after being slashed", async () => { - let slasheeIdx = 2; - let updateValidatorTx: ContractTransaction; - let slashValidatorTx: ContractTransaction; + it('Should the Staking contract emit Unstaked event', async () => { + await expect(slashValidatorTx) + .to.emit(stakingContract, 'Unstaked') + .withArgs(slashee.address, slashFelonyAmount); + }); - before(async () => { - await stakingContract - .connect(validatorCandidates[slasheeIdx]) - .proposeValidator(validatorCandidates[slasheeIdx].address, validatorCandidates[slasheeIdx].address, 2_00, { - value: minValidatorBalance.add(slashFelonyAmount.mul(10)), - }); + it('Should the Staking contract emit Undelegated event', async () => { + await expect(slashValidatorTx) + .to.emit(stakingContract, 'Undelegated') + .withArgs(slashee.address, slashee.address, slashFelonyAmount); + }); - await mineBatchTxs(async () => { - await roninValidatorSetContract.connect(coinbase).endEpoch(); - updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); - }); - await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, [ - validatorCandidates[slasheeIdx].address, - ]); + it('Should the Staking contract subtract staked amount from validator', async () => { + let _expectingSlasheeStakingAmount = slasheeInitStakingAmount.sub(slashFelonyAmount); + expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(_expectingSlasheeStakingAmount); + }); - expect(await roninValidatorSetContract.getValidators()).eql([validatorCandidates[slasheeIdx].address]); + it('Should the validator set exclude the jailed validator in the next epoch', async () => { + await mineBatchTxs(async () => { + await roninValidatorSetContract.connect(coinbase).endEpoch(); + updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); }); + expectingValidatorSet.pop(); + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); + }); - it('Should the ValidatorSet contract emit event', async () => { - for (let i = 0; i < felonyThreshold - 1; i++) { - await slashIndicatorContract.connect(coinbase).slash(validatorCandidates[slasheeIdx].address); - } - slashValidatorTx = await slashIndicatorContract - .connect(coinbase) - .slash(validatorCandidates[slasheeIdx].address); - - await expect(slashValidatorTx) - .to.emit(slashIndicatorContract, 'ValidatorSlashed') - .withArgs(validatorCandidates[slasheeIdx].address, SlashType.FELONY); - - let blockNumber = await network.provider.send('eth_blockNumber'); - - await expect(slashValidatorTx) - .to.emit(roninValidatorSetContract, 'ValidatorSlashed') - .withArgs( - validatorCandidates[slasheeIdx].address, - BigNumber.from(blockNumber).add(felonyJailDuration), - slashFelonyAmount - ); + it('Should the validator candidate cannot re-join as a validator when jail time is not over', async () => { + let _blockNumber = await network.provider.send('eth_blockNumber'); + let _jailUntil = await roninValidatorSetContract.getJailUntils([slashee.address]); + let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber).sub(100); + + await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); + await mineBatchTxs(async () => { + await roninValidatorSetContract.connect(coinbase).endEpoch(); + updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); }); - it('Should the validator is put in jail', async () => { - let blockNumber = await network.provider.send('eth_blockNumber'); - expect(await roninValidatorSetContract.getJailUntils([validatorCandidates[slasheeIdx].address])).eql([ - BigNumber.from(blockNumber).add(felonyJailDuration), - ]); + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, []); + }); + + it('Should the validator candidate re-join as a validator when jail time is over', async () => { + let _blockNumber = await network.provider.send('eth_blockNumber'); + let _jailUntil = await roninValidatorSetContract.getJailUntils([slashee.address]); + let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber); + + await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); + await mineBatchTxs(async () => { + await roninValidatorSetContract.connect(coinbase).endEpoch(); + updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); }); + expectingValidatorSet.push(slashee.address); + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); + }); + }); + + describe('Slash felony validator -- when the validators balance is equal to minimum balance', async () => { + let updateValidatorTx: ContractTransaction; + let slashValidatorTx: ContractTransaction; + let slasheeIdx: number; + let slashee: SignerWithAddress; + let slasheeInitStakingAmount: BigNumber; + + before(async () => { + slasheeIdx = 3; + slashee = validatorCandidates[slasheeIdx]; + slasheeInitStakingAmount = minValidatorBalance; - it('Should the validator get subtracted staked amount', async () => {}); + await stakingContract.connect(slashee).proposeValidator(slashee.address, slashee.address, 2_00, { + value: slasheeInitStakingAmount, + }); - it('Should the validator set exclude the jailed validator in the next epoch', async () => {}); + expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(slasheeInitStakingAmount); - it('Should the validator candidate cannot join as a validator when jail time is over, due to insufficient fund', async () => {}); + await mineBatchTxs(async () => { + await roninValidatorSetContract.connect(coinbase).endEpoch(); + updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + }); + expectingValidatorSet.push(slashee.address); + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); - it('Should the validator top-up balance and be able to re-join the validator set', async () => {}); + expect(await roninValidatorSetContract.getValidators()).eql(expectingValidatorSet); }); - describe.skip("Slash felony validator -- when the validator's balance is equal to minimum balance", async () => {}); + it('Should the ValidatorSet contract emit event', async () => { + for (let i = 0; i < felonyThreshold - 1; i++) { + await slashIndicatorContract.connect(coinbase).slash(slashee.address); + } + slashValidatorTx = await slashIndicatorContract.connect(coinbase).slash(slashee.address); + + await expect(slashValidatorTx) + .to.emit(slashIndicatorContract, 'ValidatorSlashed') + .withArgs(slashee.address, SlashType.FELONY); + + let blockNumber = await network.provider.send('eth_blockNumber'); + + await expect(slashValidatorTx) + .to.emit(roninValidatorSetContract, 'ValidatorSlashed') + .withArgs(slashee.address, BigNumber.from(blockNumber).add(felonyJailDuration), slashFelonyAmount); + }); + + it('Should the validator is put in jail', async () => { + let blockNumber = await network.provider.send('eth_blockNumber'); + expect(await roninValidatorSetContract.getJailUntils([slashee.address])).eql([ + BigNumber.from(blockNumber).add(felonyJailDuration), + ]); + }); + + it('Should the Staking contract emit Unstaked event', async () => { + await expect(slashValidatorTx) + .to.emit(stakingContract, 'Unstaked') + .withArgs(slashee.address, slashFelonyAmount); + }); + + it('Should the Staking contract emit Undelegated event', async () => { + await expect(slashValidatorTx) + .to.emit(stakingContract, 'Undelegated') + .withArgs(slashee.address, slashee.address, slashFelonyAmount); + }); + + it('Should the Staking contract subtract staked amount from validator', async () => { + let _expectingSlasheeStakingAmount = slasheeInitStakingAmount.sub(slashFelonyAmount); + expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(_expectingSlasheeStakingAmount); + }); + + it('Should the validator set exclude the jailed validator in the next epoch', async () => { + await mineBatchTxs(async () => { + await roninValidatorSetContract.connect(coinbase).endEpoch(); + updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + }); + expectingValidatorSet.pop(); + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); + }); + + it('Should the validator candidate cannot re-join as a validator when jail time is not over', async () => { + let _blockNumber = await network.provider.send('eth_blockNumber'); + let _jailUntil = await roninValidatorSetContract.getJailUntils([slashee.address]); + let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber).sub(100); + + await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); + await mineBatchTxs(async () => { + await roninValidatorSetContract.connect(coinbase).endEpoch(); + updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + }); + + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); + }); + + it('Should the jail time end', async () => { + let _blockNumber = await network.provider.send('eth_blockNumber'); + let _jailUntil = await roninValidatorSetContract.getJailUntils([slashee.address]); + let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber); + + await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); + await mineBatchTxs(async () => { + await roninValidatorSetContract.connect(coinbase).endEpoch(); + updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + }); + // TODO(thor): Fix this assertion when update contract to check minimum amount on updating validator set + expectingValidatorSet.push(slashee.address); + }); + + it.skip('Should the validator candidate cannot join as a validator when jail time is over, due to insufficient fund', async () => { + // TODO(thor): Fix this assertion when update contract to check minimum amount on updating validator set + // expectingValidatorSet.pop(); + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); + }); + + it('Should the validator top-up balance for being sufficient minimum balance of a validator', async () => { + let topUpTx = await stakingContract.connect(slashee).stake(slashee.address, { + value: slashFelonyAmount, + }); + + expect(topUpTx).to.emit(stakingContract, 'Staked').withArgs(slashee.address, slashFelonyAmount); + expect(topUpTx).to.emit(stakingContract, 'Delegated').withArgs(slashee.address, slashFelonyAmount); + }); + + it('Should the validator be able to re-join the validator set', async () => { + await mineBatchTxs(async () => { + await roninValidatorSetContract.connect(coinbase).endEpoch(); + updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + }); + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); + }); }); }); }); From 5488f2416181a843af584095d9197401b27df7e9 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 12 Sep 2022 13:57:17 +0700 Subject: [PATCH 108/190] [Test] Add configuration test --- contracts/interfaces/IStaking.sol | 5 + contracts/staking/Staking.sol | 4 + test/integration/ActionSlashValidators.ts | 125 ++++++++++---------- test/integration/ActionSubmitReward.ts | 133 ++++++++++++++++++++++ test/integration/Configuration.ts | 98 ++++++++++++++++ 5 files changed, 308 insertions(+), 57 deletions(-) create mode 100644 test/integration/ActionSubmitReward.ts create mode 100644 test/integration/Configuration.ts diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 7b308ab01..9ba25a5c1 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -66,6 +66,11 @@ interface IStaking is IRewardPool { */ function governanceAdmin() external view returns (address); + /** + * @dev Returns validator contracts + */ + function validatorContract() external view returns (address); + /** * @dev Returns the minimum threshold for being a validator candidate. */ diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 9bbfc546e..bda36ce37 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -70,6 +70,10 @@ contract Staking is IStaking, StakingManager, Initializable { return _governanceAdmin; } + function validatorContract() external view override returns (address) { + return _validatorContract; + } + /** * @inheritdoc IStaking */ diff --git a/test/integration/ActionSlashValidators.ts b/test/integration/ActionSlashValidators.ts index 3f3d2d148..50f6217fc 100644 --- a/test/integration/ActionSlashValidators.ts +++ b/test/integration/ActionSlashValidators.ts @@ -18,9 +18,9 @@ import { expects as RoninValidatorSetExpects } from '../../src/script/ronin-vali import { mineBatchTxs } from '../utils'; import { Address } from 'hardhat-deploy/dist/types'; -let slashIndicatorContract: SlashIndicator; +let slashContract: SlashIndicator; let stakingContract: Staking; -let roninValidatorSetContract: MockRoninValidatorSetEpochSetterAndQueryInfo; +let validatorContract: MockRoninValidatorSetEpochSetterAndQueryInfo; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; @@ -84,15 +84,15 @@ describe('[Integration] Slash validators', () => { ]) ); await slashProxyContract.deployed(); - slashIndicatorContract = SlashIndicator__factory.connect(slashProxyContract.address, deployer); + slashContract = SlashIndicator__factory.connect(slashProxyContract.address, deployer); - roninValidatorSetContract = await new MockRoninValidatorSetEpochSetterAndQueryInfo__factory(deployer).deploy( + validatorContract = await new MockRoninValidatorSetEpochSetterAndQueryInfo__factory(deployer).deploy( governanceAdmin.address, - slashIndicatorContract.address, + slashContract.address, stakingContractAddr, maxValidatorNumber ); - await roninValidatorSetContract.deployed(); + await validatorContract.deployed(); const stakingLogicContract = await new Staking__factory(deployer).deploy(); await stakingLogicContract.deployed(); @@ -101,7 +101,7 @@ describe('[Integration] Slash validators', () => { stakingLogicContract.address, proxyAdmin.address, stakingLogicContract.interface.encodeFunctionData('initialize', [ - roninValidatorSetContract.address, + validatorContract.address, governanceAdmin.address, 100, minValidatorBalance, @@ -110,24 +110,35 @@ describe('[Integration] Slash validators', () => { await stakingProxyContract.deployed(); stakingContract = Staking__factory.connect(stakingProxyContract.address, deployer); - expect(roninValidatorSetAddr.toLowerCase()).eq(roninValidatorSetContract.address.toLowerCase()); + expect(roninValidatorSetAddr.toLowerCase()).eq(validatorContract.address.toLowerCase()); expect(stakingContractAddr.toLowerCase()).eq(stakingContract.address.toLowerCase()); }); describe('Configuration test', async () => { - it('Should the SlashContract config the ValidatorSetContract correctly', async () => { - let _validatorContract = await slashIndicatorContract.validatorContract(); - expect(_validatorContract).to.eq(roninValidatorSetContract.address); + describe('ValidatorSetContract configuration', async () => { + it('Should the ValidatorSetContract config the StakingContract correctly', async () => { + let _stakingContract = await validatorContract.stakingContract(); + expect(_stakingContract).to.eq(stakingContract.address); + }); + + it('Should the ValidatorSetContract config the Slashing correctly', async () => { + let _slashingContract = await validatorContract.slashIndicatorContract(); + expect(_slashingContract).to.eq(slashContract.address); + }); }); - it('Should the ValidatorSetContract config the SlashContract correctly', async () => { - let _slashContract = await roninValidatorSetContract.slashIndicatorContract(); - expect(_slashContract).to.eq(slashIndicatorContract.address); + describe('StakingContract configuration', async () => { + it('Should the StakingContract config the ValidatorSetContract correctly', async () => { + let _validatorSetContract = await stakingContract.validatorContract(); + expect(_validatorSetContract).to.eq(validatorContract.address); + }); }); - it('Should the ValidatorSetContract config the StakingContract correctly', async () => { - let _stakingContract = await roninValidatorSetContract.stakingContract(); - expect(_stakingContract).to.eq(stakingContract.address); + describe('SlashIndicatorContract configuration', async () => { + it('Should the SlashIndicatorContract config the ValidatorSetContract correctly', async () => { + let _validatorSetContract = await slashContract.validatorContract(); + expect(_validatorSetContract).to.eq(validatorContract.address); + }); }); }); @@ -140,15 +151,13 @@ describe('[Integration] Slash validators', () => { let slashee = validatorCandidates[slasheeIdx]; for (let i = 0; i < misdemeanorThreshold - 1; i++) { - await slashIndicatorContract.connect(coinbase).slash(slashee.address); + await slashContract.connect(coinbase).slash(slashee.address); } - let tx = slashIndicatorContract.connect(coinbase).slash(slashee.address); + let tx = slashContract.connect(coinbase).slash(slashee.address); - await expect(tx) - .to.emit(slashIndicatorContract, 'ValidatorSlashed') - .withArgs(slashee.address, SlashType.MISDEMEANOR); + await expect(tx).to.emit(slashContract, 'ValidatorSlashed').withArgs(slashee.address, SlashType.MISDEMEANOR); - await expect(tx).to.emit(roninValidatorSetContract, 'ValidatorSlashed').withArgs(slashee.address, 0, 0); + await expect(tx).to.emit(validatorContract, 'ValidatorSlashed').withArgs(slashee.address, 0, 0); }); }); @@ -170,36 +179,36 @@ describe('[Integration] Slash validators', () => { expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(slasheeInitStakingAmount); await mineBatchTxs(async () => { - await roninValidatorSetContract.connect(coinbase).endEpoch(); - updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + await validatorContract.connect(coinbase).endEpoch(); + updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); expectingValidatorSet.push(slashee.address); await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); - expect(await roninValidatorSetContract.getValidators()).eql(expectingValidatorSet); + expect(await validatorContract.getValidators()).eql(expectingValidatorSet); }); it('Should the ValidatorSet contract emit event', async () => { for (let i = 0; i < felonyThreshold - 1; i++) { - await slashIndicatorContract.connect(coinbase).slash(slashee.address); + await slashContract.connect(coinbase).slash(slashee.address); } - slashValidatorTx = await slashIndicatorContract.connect(coinbase).slash(slashee.address); + slashValidatorTx = await slashContract.connect(coinbase).slash(slashee.address); await expect(slashValidatorTx) - .to.emit(slashIndicatorContract, 'ValidatorSlashed') + .to.emit(slashContract, 'ValidatorSlashed') .withArgs(slashee.address, SlashType.FELONY); let blockNumber = await network.provider.send('eth_blockNumber'); await expect(slashValidatorTx) - .to.emit(roninValidatorSetContract, 'ValidatorSlashed') + .to.emit(validatorContract, 'ValidatorSlashed') .withArgs(slashee.address, BigNumber.from(blockNumber).add(felonyJailDuration), slashFelonyAmount); }); it('Should the validator is put in jail', async () => { let blockNumber = await network.provider.send('eth_blockNumber'); - expect(await roninValidatorSetContract.getJailUntils(expectingValidatorSet)).eql([ + expect(await validatorContract.getJailUntils(expectingValidatorSet)).eql([ BigNumber.from(blockNumber).add(felonyJailDuration), ]); }); @@ -223,8 +232,8 @@ describe('[Integration] Slash validators', () => { it('Should the validator set exclude the jailed validator in the next epoch', async () => { await mineBatchTxs(async () => { - await roninValidatorSetContract.connect(coinbase).endEpoch(); - updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + await validatorContract.connect(coinbase).endEpoch(); + updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); expectingValidatorSet.pop(); await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); @@ -232,13 +241,13 @@ describe('[Integration] Slash validators', () => { it('Should the validator candidate cannot re-join as a validator when jail time is not over', async () => { let _blockNumber = await network.provider.send('eth_blockNumber'); - let _jailUntil = await roninValidatorSetContract.getJailUntils([slashee.address]); + let _jailUntil = await validatorContract.getJailUntils([slashee.address]); let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber).sub(100); await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); await mineBatchTxs(async () => { - await roninValidatorSetContract.connect(coinbase).endEpoch(); - updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + await validatorContract.connect(coinbase).endEpoch(); + updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, []); @@ -246,13 +255,13 @@ describe('[Integration] Slash validators', () => { it('Should the validator candidate re-join as a validator when jail time is over', async () => { let _blockNumber = await network.provider.send('eth_blockNumber'); - let _jailUntil = await roninValidatorSetContract.getJailUntils([slashee.address]); + let _jailUntil = await validatorContract.getJailUntils([slashee.address]); let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber); await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); await mineBatchTxs(async () => { - await roninValidatorSetContract.connect(coinbase).endEpoch(); - updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + await validatorContract.connect(coinbase).endEpoch(); + updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); expectingValidatorSet.push(slashee.address); await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); @@ -278,35 +287,35 @@ describe('[Integration] Slash validators', () => { expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(slasheeInitStakingAmount); await mineBatchTxs(async () => { - await roninValidatorSetContract.connect(coinbase).endEpoch(); - updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + await validatorContract.connect(coinbase).endEpoch(); + updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); expectingValidatorSet.push(slashee.address); await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); - expect(await roninValidatorSetContract.getValidators()).eql(expectingValidatorSet); + expect(await validatorContract.getValidators()).eql(expectingValidatorSet); }); it('Should the ValidatorSet contract emit event', async () => { for (let i = 0; i < felonyThreshold - 1; i++) { - await slashIndicatorContract.connect(coinbase).slash(slashee.address); + await slashContract.connect(coinbase).slash(slashee.address); } - slashValidatorTx = await slashIndicatorContract.connect(coinbase).slash(slashee.address); + slashValidatorTx = await slashContract.connect(coinbase).slash(slashee.address); await expect(slashValidatorTx) - .to.emit(slashIndicatorContract, 'ValidatorSlashed') + .to.emit(slashContract, 'ValidatorSlashed') .withArgs(slashee.address, SlashType.FELONY); let blockNumber = await network.provider.send('eth_blockNumber'); await expect(slashValidatorTx) - .to.emit(roninValidatorSetContract, 'ValidatorSlashed') + .to.emit(validatorContract, 'ValidatorSlashed') .withArgs(slashee.address, BigNumber.from(blockNumber).add(felonyJailDuration), slashFelonyAmount); }); it('Should the validator is put in jail', async () => { let blockNumber = await network.provider.send('eth_blockNumber'); - expect(await roninValidatorSetContract.getJailUntils([slashee.address])).eql([ + expect(await validatorContract.getJailUntils([slashee.address])).eql([ BigNumber.from(blockNumber).add(felonyJailDuration), ]); }); @@ -330,8 +339,8 @@ describe('[Integration] Slash validators', () => { it('Should the validator set exclude the jailed validator in the next epoch', async () => { await mineBatchTxs(async () => { - await roninValidatorSetContract.connect(coinbase).endEpoch(); - updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + await validatorContract.connect(coinbase).endEpoch(); + updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); expectingValidatorSet.pop(); await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); @@ -339,13 +348,13 @@ describe('[Integration] Slash validators', () => { it('Should the validator candidate cannot re-join as a validator when jail time is not over', async () => { let _blockNumber = await network.provider.send('eth_blockNumber'); - let _jailUntil = await roninValidatorSetContract.getJailUntils([slashee.address]); + let _jailUntil = await validatorContract.getJailUntils([slashee.address]); let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber).sub(100); await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); await mineBatchTxs(async () => { - await roninValidatorSetContract.connect(coinbase).endEpoch(); - updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + await validatorContract.connect(coinbase).endEpoch(); + updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); @@ -353,13 +362,13 @@ describe('[Integration] Slash validators', () => { it('Should the jail time end', async () => { let _blockNumber = await network.provider.send('eth_blockNumber'); - let _jailUntil = await roninValidatorSetContract.getJailUntils([slashee.address]); + let _jailUntil = await validatorContract.getJailUntils([slashee.address]); let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber); await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); await mineBatchTxs(async () => { - await roninValidatorSetContract.connect(coinbase).endEpoch(); - updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + await validatorContract.connect(coinbase).endEpoch(); + updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); // TODO(thor): Fix this assertion when update contract to check minimum amount on updating validator set expectingValidatorSet.push(slashee.address); @@ -382,11 +391,13 @@ describe('[Integration] Slash validators', () => { it('Should the validator be able to re-join the validator set', async () => { await mineBatchTxs(async () => { - await roninValidatorSetContract.connect(coinbase).endEpoch(); - updateValidatorTx = await roninValidatorSetContract.connect(coinbase).wrapUpEpoch(); + await validatorContract.connect(coinbase).endEpoch(); + updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); }); }); }); + + // TODO(Bao): Test for reward amount of validators and delegators }); diff --git a/test/integration/ActionSubmitReward.ts b/test/integration/ActionSubmitReward.ts new file mode 100644 index 000000000..b757db2f0 --- /dev/null +++ b/test/integration/ActionSubmitReward.ts @@ -0,0 +1,133 @@ +import { expect } from 'chai'; +import { network, ethers } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; + +import { + SlashIndicator, + SlashIndicator__factory, + Staking, + Staking__factory, + MockRoninValidatorSetEpochSetterAndQueryInfo__factory, + MockRoninValidatorSetEpochSetterAndQueryInfo, + TransparentUpgradeableProxy__factory, +} from '../../src/types'; +import { Network, slashIndicatorConf, roninValidatorSetConf, stakingConfig, initAddress } from '../../src/config'; +import { BigNumber } from 'ethers'; + +let slashContract: SlashIndicator; +let stakingContract: Staking; +let validatorContract: MockRoninValidatorSetEpochSetterAndQueryInfo; + +let coinbase: SignerWithAddress; +let deployer: SignerWithAddress; +let governanceAdmin: SignerWithAddress; +let proxyAdmin: SignerWithAddress; +let validatorCandidates: SignerWithAddress[]; + +const slashFelonyAmount = BigNumber.from(1); +const slashDoubleSignAmount = 1000; +const maxValidatorNumber = 4; +const minValidatorBalance = BigNumber.from(100); +const felonyJailDuration = 28800 * 2; +const misdemeanorThreshold = 10; +const felonyThreshold = 20; + +describe('[Integration] Submit Block Reward', () => { + before(async () => { + [coinbase, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + validatorCandidates = validatorCandidates.slice(0, 5); + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + + if (network.name == Network.Hardhat) { + initAddress[network.name] = { + governanceAdmin: governanceAdmin.address, + }; + slashIndicatorConf[network.name] = { + misdemeanorThreshold: misdemeanorThreshold, + felonyThreshold: felonyThreshold, + slashFelonyAmount: slashFelonyAmount, + slashDoubleSignAmount: slashDoubleSignAmount, + felonyJailBlocks: felonyJailDuration, + }; + roninValidatorSetConf[network.name] = { + maxValidatorNumber: 21, + numberOfBlocksInEpoch: 600, + numberOfEpochsInPeriod: 48, // 1 day + }; + stakingConfig[network.name] = { + minValidatorBalance: minValidatorBalance, + maxValidatorCandidate: maxValidatorNumber, + }; + } + + const nonce = await deployer.getTransactionCount(); + const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); + const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); + + const slashLogicContract = await new SlashIndicator__factory(deployer).deploy(); + await slashLogicContract.deployed(); + + const slashProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + slashLogicContract.address, + proxyAdmin.address, + slashLogicContract.interface.encodeFunctionData('initialize', [ + slashIndicatorConf[network.name]!.misdemeanorThreshold, + slashIndicatorConf[network.name]!.felonyThreshold, + roninValidatorSetAddr, + slashIndicatorConf[network.name]!.slashFelonyAmount, + slashIndicatorConf[network.name]!.slashDoubleSignAmount, + slashIndicatorConf[network.name]!.felonyJailBlocks, + ]) + ); + await slashProxyContract.deployed(); + slashContract = SlashIndicator__factory.connect(slashProxyContract.address, deployer); + + validatorContract = await new MockRoninValidatorSetEpochSetterAndQueryInfo__factory(deployer).deploy( + governanceAdmin.address, + slashContract.address, + stakingContractAddr, + maxValidatorNumber + ); + await validatorContract.deployed(); + + const stakingLogicContract = await new Staking__factory(deployer).deploy(); + await stakingLogicContract.deployed(); + + const stakingProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + stakingLogicContract.address, + proxyAdmin.address, + stakingLogicContract.interface.encodeFunctionData('initialize', [ + validatorContract.address, + governanceAdmin.address, + 100, + minValidatorBalance, + ]) + ); + await stakingProxyContract.deployed(); + stakingContract = Staking__factory.connect(stakingProxyContract.address, deployer); + + expect(roninValidatorSetAddr.toLowerCase()).eq(validatorContract.address.toLowerCase()); + expect(stakingContractAddr.toLowerCase()).eq(stakingContract.address.toLowerCase()); + }); + + describe('Configuration check', async () => { + describe('ValidatorSetContract configuration', async () => { + it('Should the ValidatorSetContract config the StakingContract correctly', async () => { + let _stakingContract = await validatorContract.stakingContract(); + expect(_stakingContract).to.eq(stakingContract.address); + }); + + it('Should the ValidatorSetContract config the Slashing correctly', async () => { + let _slashingContract = await validatorContract.slashIndicatorContract(); + expect(_slashingContract).to.eq(slashContract.address); + }); + }); + + describe('StakingContract configuration', async () => { + it('Should the StakingContract config the ValidatorSetContract correctly', async () => { + let _validatorSetContract = await stakingContract.validatorContract(); + expect(_validatorSetContract).to.eq(validatorContract.address); + }); + }); + }); +}); diff --git a/test/integration/Configuration.ts b/test/integration/Configuration.ts new file mode 100644 index 000000000..713e17d11 --- /dev/null +++ b/test/integration/Configuration.ts @@ -0,0 +1,98 @@ +import { expect } from 'chai'; +import { network, ethers, deployments } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; + +import { + SlashIndicator, + SlashIndicator__factory, + Staking, + Staking__factory, + RoninValidatorSet, + RoninValidatorSet__factory, + TransparentUpgradeableProxy__factory, +} from '../../src/types'; +import { Network, slashIndicatorConf, roninValidatorSetConf, stakingConfig, initAddress } from '../../src/config'; +import { BigNumber } from 'ethers'; + +let slashContract: SlashIndicator; +let stakingContract: Staking; +let validatorContract: RoninValidatorSet; + +let coinbase: SignerWithAddress; +let deployer: SignerWithAddress; +let governanceAdmin: SignerWithAddress; +let proxyAdmin: SignerWithAddress; +let validatorCandidates: SignerWithAddress[]; + +const slashFelonyAmount = BigNumber.from(1); +const slashDoubleSignAmount = 1000; +const maxValidatorNumber = 4; +const minValidatorBalance = BigNumber.from(100); +const felonyJailDuration = 28800 * 2; +const misdemeanorThreshold = 10; +const felonyThreshold = 20; + +describe('[Integration] Configuration check', () => { + before(async () => { + [coinbase, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + + if (network.name == Network.Hardhat) { + initAddress[network.name] = { + governanceAdmin: governanceAdmin.address, + }; + slashIndicatorConf[network.name] = { + misdemeanorThreshold: misdemeanorThreshold, + felonyThreshold: felonyThreshold, + slashFelonyAmount: slashFelonyAmount, + slashDoubleSignAmount: slashDoubleSignAmount, + felonyJailBlocks: felonyJailDuration, + }; + roninValidatorSetConf[network.name] = { + maxValidatorNumber: 21, + numberOfBlocksInEpoch: 600, + numberOfEpochsInPeriod: 48, // 1 day + }; + stakingConfig[network.name] = { + minValidatorBalance: minValidatorBalance, + maxValidatorCandidate: maxValidatorNumber, + }; + } + + await deployments.fixture(['CalculateAddresses', 'RoninValidatorSetProxy', 'SlashIndicatorProxy', 'StakingProxy']); + + const slashContractDeployment = await deployments.get('SlashIndicatorProxy'); + slashContract = SlashIndicator__factory.connect(slashContractDeployment.address, deployer); + + const stakingContractDeployment = await deployments.get('StakingProxy'); + stakingContract = Staking__factory.connect(stakingContractDeployment.address, deployer); + + const validatorContractDeployment = await deployments.get('RoninValidatorSetProxy'); + validatorContract = RoninValidatorSet__factory.connect(validatorContractDeployment.address, deployer); + }); + + describe('ValidatorSetContract configuration', async () => { + it('Should the ValidatorSetContract config the StakingContract correctly', async () => { + let _stakingContract = await validatorContract.stakingContract(); + expect(_stakingContract).to.eq(stakingContract.address); + }); + + it('Should the ValidatorSetContract config the Slashing correctly', async () => { + let _slashingContract = await validatorContract.slashIndicatorContract(); + expect(_slashingContract).to.eq(slashContract.address); + }); + }); + + describe('StakingContract configuration', async () => { + it('Should the StakingContract config the ValidatorSetContract correctly', async () => { + let _validatorSetContract = await stakingContract.validatorContract(); + expect(_validatorSetContract).to.eq(validatorContract.address); + }); + }); + + describe('SlashIndicatorContract configuration', async () => { + it('Should the SlashIndicatorContract config the ValidatorSetContract correctly', async () => { + let _validatorSetContract = await slashContract.validatorContract(); + expect(_validatorSetContract).to.eq(validatorContract.address); + }); + }); +}); From af3aa27a0f037c16d3a2f3f5d1f09bdcd1ad34c2 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 12 Sep 2022 15:16:11 +0700 Subject: [PATCH 109/190] [IntegrationTest] Fix slashing test --- test/integration/ActionSlashValidators.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/integration/ActionSlashValidators.ts b/test/integration/ActionSlashValidators.ts index 50f6217fc..3a96e6c33 100644 --- a/test/integration/ActionSlashValidators.ts +++ b/test/integration/ActionSlashValidators.ts @@ -385,8 +385,10 @@ describe('[Integration] Slash validators', () => { value: slashFelonyAmount, }); - expect(topUpTx).to.emit(stakingContract, 'Staked').withArgs(slashee.address, slashFelonyAmount); - expect(topUpTx).to.emit(stakingContract, 'Delegated').withArgs(slashee.address, slashFelonyAmount); + await expect(topUpTx).to.emit(stakingContract, 'Staked').withArgs(slashee.address, slashFelonyAmount); + await expect(topUpTx) + .to.emit(stakingContract, 'Delegated') + .withArgs(slashee.address, slashee.address, slashFelonyAmount); }); it('Should the validator be able to re-join the validator set', async () => { From 2485e1125a6be75e9b3cf43ad80de111950d7f59 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 12 Sep 2022 15:23:46 +0700 Subject: [PATCH 110/190] [IntegrationTest] Add more test for submit reward --- test/integration/ActionSubmitReward.ts | 87 +++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/test/integration/ActionSubmitReward.ts b/test/integration/ActionSubmitReward.ts index b757db2f0..17eab9fb2 100644 --- a/test/integration/ActionSubmitReward.ts +++ b/test/integration/ActionSubmitReward.ts @@ -12,7 +12,8 @@ import { TransparentUpgradeableProxy__factory, } from '../../src/types'; import { Network, slashIndicatorConf, roninValidatorSetConf, stakingConfig, initAddress } from '../../src/config'; -import { BigNumber } from 'ethers'; +import { BigNumber, ContractTransaction } from 'ethers'; +import { mineBatchTxs } from '../utils'; let slashContract: SlashIndicator; let stakingContract: Staking; @@ -31,6 +32,7 @@ const minValidatorBalance = BigNumber.from(100); const felonyJailDuration = 28800 * 2; const misdemeanorThreshold = 10; const felonyThreshold = 20; +const blockRewardAmount = BigNumber.from(2); describe('[Integration] Submit Block Reward', () => { before(async () => { @@ -130,4 +132,87 @@ describe('[Integration] Submit Block Reward', () => { }); }); }); + + describe('One validator submits block reward', async () => { + let validator: SignerWithAddress; + let submitRewardTx: ContractTransaction; + + before(async () => { + let initStakingAmount = minValidatorBalance.mul(2); + validator = validatorCandidates[0]; + await stakingContract.connect(validator).proposeValidator(validator.address, validator.address, 2_00, { + value: initStakingAmount, + }); + await mineBatchTxs(async () => { + await validatorContract.connect(coinbase).endEpoch(); + await validatorContract.connect(coinbase).wrapUpEpoch(); + }); + }); + + it('Should validator can submit block reward', async () => { + await network.provider.send('hardhat_setCoinbase', [validator.address]); + validatorContract = validatorContract.connect(validator); + + submitRewardTx = await validatorContract.submitBlockReward({ + value: blockRewardAmount, + }); + }); + + it('Should the ValidatorSetContract emit event of submitting reward', async () => { + await expect(submitRewardTx) + .to.emit(validatorContract, 'BlockRewardSubmitted') + .withArgs(validator.address, blockRewardAmount); + }); + + it.skip('Should the StakingContract emit event of recording reward', async () => { + await expect(submitRewardTx).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(validator.address); + }); + + it('Should the StakingContract record update for new block reward', async () => {}); + + after(async () => { + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + }); + }); + + describe('In-jail validator submits block reward', async () => { + let validator: SignerWithAddress; + let submitRewardTx: ContractTransaction; + + before(async () => { + let initStakingAmount = minValidatorBalance.mul(2); + validator = validatorCandidates[1]; + await stakingContract.connect(validator).proposeValidator(validator.address, validator.address, 2_00, { + value: initStakingAmount, + }); + + await mineBatchTxs(async () => { + await validatorContract.connect(coinbase).endEpoch(); + await validatorContract.connect(coinbase).wrapUpEpoch(); + }); + + for (let i = 0; i < felonyThreshold; i++) { + await slashContract.connect(coinbase).slash(validator.address); + } + }); + + it('Should in-jail validator cannot submit block reward', async () => { + await network.provider.send('hardhat_setCoinbase', [validator.address]); + validatorContract = validatorContract.connect(validator); + + submitRewardTx = await validatorContract.submitBlockReward({ + value: blockRewardAmount, + }); + }); + + it('Should the ValidatorSetContract emit event of deprecating reward', async () => { + await expect(submitRewardTx) + .to.emit(validatorContract, 'RewardDeprecated') + .withArgs(validator.address, blockRewardAmount); + }); + + it('Should the StakingContract emit event of recording reward', async () => { + expect(submitRewardTx).not.to.emit(stakingContract, 'PendingPoolUpdated'); + }); + }); }); From d51b24426a2b592da53a58621aba2c0166546553 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 12 Sep 2022 15:24:24 +0700 Subject: [PATCH 111/190] [IntegrationTest] Rename files --- .../{ActionSlashValidators.ts => ActionSlashValidators.test.ts} | 0 .../{ActionSubmitReward.ts => ActionSubmitReward.test.ts} | 0 test/integration/ActionWrapUpEpoch.test.ts | 0 test/integration/{Configuration.ts => Configuration.test.ts} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename test/integration/{ActionSlashValidators.ts => ActionSlashValidators.test.ts} (100%) rename test/integration/{ActionSubmitReward.ts => ActionSubmitReward.test.ts} (100%) create mode 100644 test/integration/ActionWrapUpEpoch.test.ts rename test/integration/{Configuration.ts => Configuration.test.ts} (100%) diff --git a/test/integration/ActionSlashValidators.ts b/test/integration/ActionSlashValidators.test.ts similarity index 100% rename from test/integration/ActionSlashValidators.ts rename to test/integration/ActionSlashValidators.test.ts diff --git a/test/integration/ActionSubmitReward.ts b/test/integration/ActionSubmitReward.test.ts similarity index 100% rename from test/integration/ActionSubmitReward.ts rename to test/integration/ActionSubmitReward.test.ts diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/Configuration.ts b/test/integration/Configuration.test.ts similarity index 100% rename from test/integration/Configuration.ts rename to test/integration/Configuration.test.ts From 0f15d6ec14e3649521f188c5ed7d6539975039d3 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 12 Sep 2022 15:36:48 +0700 Subject: [PATCH 112/190] [package] Add hardhat-chai-matchers, remove ethereum-waffle --- hardhat.config.ts | 1 + package.json | 6 +- yarn.lock | 4745 ++------------------------------------------- 3 files changed, 122 insertions(+), 4630 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 125af326e..85bb8003f 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -3,6 +3,7 @@ import '@nomiclabs/hardhat-waffle'; import '@nomiclabs/hardhat-ethers'; import 'hardhat-deploy'; import 'hardhat-gas-reporter'; +import '@nomicfoundation/hardhat-chai-matchers'; import * as dotenv from 'dotenv'; import { HardhatUserConfig, NetworkUserConfig, SolcUserConfig } from 'hardhat/types'; diff --git a/package.json b/package.json index f0ab2ad8c..cc4aa9523 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,10 @@ "hardhat.config.{js,ts}": "prettier --write" }, "dependencies": { - "@openzeppelin/contracts": "^4.6.0", - "dotenv": "^10.0.0" + "@openzeppelin/contracts": "^4.6.0" }, "devDependencies": { + "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", "@nomiclabs/hardhat-ethers": "^2.0.3", "@nomiclabs/hardhat-waffle": "^2.0.1", "@typechain/ethers-v5": "^8.0.5", @@ -32,7 +32,7 @@ "@types/mocha": "^9.0.0", "@types/node": "^17.0.0", "chai": "^4.3.4", - "ethereum-waffle": "^3.4.0", + "dotenv": "^10.0.0", "ethers": "^5.5.2", "hardhat": "^2.7.1", "hardhat-deploy": "^0.9.14", diff --git a/yarn.lock b/yarn.lock index fcc3f456b..97354dcc3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -30,75 +30,6 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@ensdomains/ens@^0.4.4": - version "0.4.5" - resolved "https://registry.yarnpkg.com/@ensdomains/ens/-/ens-0.4.5.tgz#e0aebc005afdc066447c6e22feb4eda89a5edbfc" - integrity sha512-JSvpj1iNMFjK6K+uVl4unqMoa9rf5jopb8cya5UGBWz23Nw8hSNT7efgUx4BTlAPAgpNlEioUfeTyQ6J9ZvTVw== - dependencies: - bluebird "^3.5.2" - eth-ens-namehash "^2.0.8" - solc "^0.4.20" - testrpc "0.0.1" - web3-utils "^1.0.0-beta.31" - -"@ensdomains/resolver@^0.2.4": - version "0.2.4" - resolved "https://registry.yarnpkg.com/@ensdomains/resolver/-/resolver-0.2.4.tgz#c10fe28bf5efbf49bff4666d909aed0265efbc89" - integrity sha512-bvaTH34PMCbv6anRa9I/0zjLJgY4EuznbEMgbV77JBCQ9KNC46rzi0avuxpOfu+xDjPEtSFGqVEOr5GlUSGudA== - -"@ethereum-waffle/chai@^3.4.4": - version "3.4.4" - resolved "https://registry.yarnpkg.com/@ethereum-waffle/chai/-/chai-3.4.4.tgz#16c4cc877df31b035d6d92486dfdf983df9138ff" - integrity sha512-/K8czydBtXXkcM9X6q29EqEkc5dN3oYenyH2a9hF7rGAApAJUpH8QBtojxOY/xQ2up5W332jqgxwp0yPiYug1g== - dependencies: - "@ethereum-waffle/provider" "^3.4.4" - ethers "^5.5.2" - -"@ethereum-waffle/compiler@^3.4.4": - version "3.4.4" - resolved "https://registry.yarnpkg.com/@ethereum-waffle/compiler/-/compiler-3.4.4.tgz#d568ee0f6029e68b5c645506079fbf67d0dfcf19" - integrity sha512-RUK3axJ8IkD5xpWjWoJgyHclOeEzDLQFga6gKpeGxiS/zBu+HB0W2FvsrrLalTFIaPw/CGYACRBSIxqiCqwqTQ== - dependencies: - "@resolver-engine/imports" "^0.3.3" - "@resolver-engine/imports-fs" "^0.3.3" - "@typechain/ethers-v5" "^2.0.0" - "@types/mkdirp" "^0.5.2" - "@types/node-fetch" "^2.5.5" - ethers "^5.0.1" - mkdirp "^0.5.1" - node-fetch "^2.6.1" - solc "^0.6.3" - ts-generator "^0.1.1" - typechain "^3.0.0" - -"@ethereum-waffle/ens@^3.4.4": - version "3.4.4" - resolved "https://registry.yarnpkg.com/@ethereum-waffle/ens/-/ens-3.4.4.tgz#db97ea2c9decbb70b9205d53de2ccbd6f3182ba1" - integrity sha512-0m4NdwWxliy3heBYva1Wr4WbJKLnwXizmy5FfSSr5PMbjI7SIGCdCB59U7/ZzY773/hY3bLnzLwvG5mggVjJWg== - dependencies: - "@ensdomains/ens" "^0.4.4" - "@ensdomains/resolver" "^0.2.4" - ethers "^5.5.2" - -"@ethereum-waffle/mock-contract@^3.4.4": - version "3.4.4" - resolved "https://registry.yarnpkg.com/@ethereum-waffle/mock-contract/-/mock-contract-3.4.4.tgz#fc6ffa18813546f4950a69f5892d4dd54b2c685a" - integrity sha512-Mp0iB2YNWYGUV+VMl5tjPsaXKbKo8MDH9wSJ702l9EBjdxFf/vBvnMBAC1Fub1lLtmD0JHtp1pq+mWzg/xlLnA== - dependencies: - "@ethersproject/abi" "^5.5.0" - ethers "^5.5.2" - -"@ethereum-waffle/provider@^3.4.4": - version "3.4.4" - resolved "https://registry.yarnpkg.com/@ethereum-waffle/provider/-/provider-3.4.4.tgz#398fc1f7eb91cc2df7d011272eacba8af0c7fffb" - integrity sha512-GK8oKJAM8+PKy2nK08yDgl4A80mFuI8zBkE0C9GqTRYQqvuxIyXoLmJ5NZU9lIwyWVv5/KsoA11BgAv2jXE82g== - dependencies: - "@ethereum-waffle/ens" "^3.4.4" - ethers "^5.5.2" - ganache-core "^2.13.2" - patch-package "^6.2.2" - postinstall-postinstall "^2.1.0" - "@ethereumjs/block@^3.5.0", "@ethereumjs/block@^3.6.2", "@ethereumjs/block@^3.6.3": version "3.6.3" resolved "https://registry.yarnpkg.com/@ethereumjs/block/-/block-3.6.3.tgz#d96cbd7af38b92ebb3424223dbf773f5ccd27f84" @@ -168,22 +99,7 @@ merkle-patricia-tree "^4.2.4" rustbn.js "~0.2.0" -"@ethersproject/abi@5.0.0-beta.153": - version "5.0.0-beta.153" - resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.0-beta.153.tgz#43a37172b33794e4562999f6e2d555b7599a8eee" - integrity sha512-aXweZ1Z7vMNzJdLpR1CZUAIgnwjrZeUSvN9syCwlBaEBUFJmFY+HHnfuTI5vIhVs/mRkfJVrbEyl51JZQqyjAg== - dependencies: - "@ethersproject/address" ">=5.0.0-beta.128" - "@ethersproject/bignumber" ">=5.0.0-beta.130" - "@ethersproject/bytes" ">=5.0.0-beta.129" - "@ethersproject/constants" ">=5.0.0-beta.128" - "@ethersproject/hash" ">=5.0.0-beta.128" - "@ethersproject/keccak256" ">=5.0.0-beta.127" - "@ethersproject/logger" ">=5.0.0-beta.129" - "@ethersproject/properties" ">=5.0.0-beta.131" - "@ethersproject/strings" ">=5.0.0-beta.130" - -"@ethersproject/abi@5.6.4", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.0", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.6.3": +"@ethersproject/abi@5.6.4", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.0", "@ethersproject/abi@^5.6.3": version "5.6.4" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.6.4.tgz#f6e01b6ed391a505932698ecc0d9e7a99ee60362" integrity sha512-TTeZUlCeIHG6527/2goZA6gW5F8Emoc7MrZDC7hhP84aRGvW3TEdTnZR08Ls88YXM1m2SuK42Osw/jSi3uO8gg== @@ -261,7 +177,7 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/properties" "^5.7.0" -"@ethersproject/address@5.6.1", "@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@^5.4.0", "@ethersproject/address@^5.6.1": +"@ethersproject/address@5.6.1", "@ethersproject/address@^5.4.0", "@ethersproject/address@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.6.1.tgz#ab57818d9aefee919c5721d28cd31fd95eff413d" integrity sha512-uOgF0kS5MJv9ZvCz7x6T2EXJSzotiybApn4XlOgoTX0xdtyVIJ7pF+6cGPxiEq/dpBiTfMiw7Yc81JcwhSYA0Q== @@ -305,7 +221,7 @@ "@ethersproject/bytes" "^5.6.1" "@ethersproject/properties" "^5.6.0" -"@ethersproject/bignumber@5.6.2", "@ethersproject/bignumber@>=5.0.0-beta.130", "@ethersproject/bignumber@^5.4.1", "@ethersproject/bignumber@^5.6.2": +"@ethersproject/bignumber@5.6.2", "@ethersproject/bignumber@^5.4.1", "@ethersproject/bignumber@^5.6.2": version "5.6.2" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.6.2.tgz#72a0717d6163fab44c47bcc82e0c550ac0315d66" integrity sha512-v7+EEUbhGqT3XJ9LMPsKvXYHFc8eHxTowFCG/HgJErmq4XHJ2WR7aeyICg3uTOAQ7Icn0GFHAohXEhxQHq4Ubw== @@ -323,7 +239,7 @@ "@ethersproject/logger" "^5.7.0" bn.js "^5.2.1" -"@ethersproject/bytes@5.6.1", "@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@^5.4.0", "@ethersproject/bytes@^5.6.1": +"@ethersproject/bytes@5.6.1", "@ethersproject/bytes@^5.4.0", "@ethersproject/bytes@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.6.1.tgz#24f916e411f82a8a60412344bf4a813b917eefe7" integrity sha512-NwQt7cKn5+ZE4uDn+X5RAXLp46E1chXoaMmrxAyA0rblpxz8t58lVkrHXoRIn0lz1joQElQ8410GqhTqMOwc6g== @@ -337,7 +253,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/constants@5.6.1", "@ethersproject/constants@>=5.0.0-beta.128", "@ethersproject/constants@^5.4.0", "@ethersproject/constants@^5.6.1": +"@ethersproject/constants@5.6.1", "@ethersproject/constants@^5.4.0", "@ethersproject/constants@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.6.1.tgz#e2e974cac160dd101cf79fdf879d7d18e8cb1370" integrity sha512-QSq9WVnZbxXYFftrjSjZDUshp6/eKp6qrtdBtUCm0QxCV5z1fG/w3kdlcsjMCQuQHUnAclKoK7XpXMezhRDOLg== @@ -367,7 +283,7 @@ "@ethersproject/properties" "^5.6.0" "@ethersproject/transactions" "^5.6.2" -"@ethersproject/hash@5.6.1", "@ethersproject/hash@>=5.0.0-beta.128", "@ethersproject/hash@^5.6.1": +"@ethersproject/hash@5.6.1", "@ethersproject/hash@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.6.1.tgz#224572ea4de257f05b4abf8ae58b03a67e99b0f4" integrity sha512-L1xAHurbaxG8VVul4ankNX5HgQ8PNCTrnVXEiFnE9xoRnaUcgfD12tZINtDinSllxPLCtGwguQxJ5E6keE84pA== @@ -433,7 +349,7 @@ aes-js "3.0.0" scrypt-js "3.0.1" -"@ethersproject/keccak256@5.6.1", "@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.6.1": +"@ethersproject/keccak256@5.6.1", "@ethersproject/keccak256@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.6.1.tgz#b867167c9b50ba1b1a92bccdd4f2d6bd168a91cc" integrity sha512-bB7DQHCTRDooZZdL3lk9wpL0+XuG3XLGHLh3cePnybsO3V0rdCAOQGpn/0R3aODmnTOOkCATJiD2hnL+5bwthA== @@ -449,7 +365,7 @@ "@ethersproject/bytes" "^5.7.0" js-sha3 "0.8.0" -"@ethersproject/logger@5.6.0", "@ethersproject/logger@>=5.0.0-beta.129", "@ethersproject/logger@^5.6.0": +"@ethersproject/logger@5.6.0", "@ethersproject/logger@^5.6.0": version "5.6.0" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.6.0.tgz#d7db1bfcc22fd2e4ab574cba0bb6ad779a9a3e7a" integrity sha512-BiBWllUROH9w+P21RzoxJKzqoqpkyM1pRnEKG69bulE9TSQD8SAIvTQqIMZmmCO8pUNkgLP1wndX1gKghSpBmg== @@ -481,7 +397,7 @@ "@ethersproject/bytes" "^5.6.1" "@ethersproject/sha2" "^5.6.1" -"@ethersproject/properties@5.6.0", "@ethersproject/properties@>=5.0.0-beta.131", "@ethersproject/properties@^5.6.0": +"@ethersproject/properties@5.6.0", "@ethersproject/properties@^5.6.0": version "5.6.0" resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.6.0.tgz#38904651713bc6bdd5bdd1b0a4287ecda920fa04" integrity sha512-szoOkHskajKePTJSZ46uHUWWkbv7TzP2ypdEK6jGMqJaEt2sb0jCgfBo0gH0m2HBpRixMuJ6TBRaQCF7a9DoCg== @@ -590,7 +506,7 @@ "@ethersproject/sha2" "^5.6.1" "@ethersproject/strings" "^5.6.1" -"@ethersproject/strings@5.6.1", "@ethersproject/strings@>=5.0.0-beta.130", "@ethersproject/strings@^5.6.1": +"@ethersproject/strings@5.6.1", "@ethersproject/strings@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.6.1.tgz#dbc1b7f901db822b5cafd4ebf01ca93c373f8952" integrity sha512-2X1Lgk6Jyfg26MUnsHiT456U9ijxKUybz8IM1Vih+NJxYtXhmvKBcHOmvGqpFSVJ0nQ4ZCoIViR8XlRw1v/+Cw== @@ -608,7 +524,7 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/transactions@5.6.2", "@ethersproject/transactions@^5.0.0-beta.135", "@ethersproject/transactions@^5.4.0", "@ethersproject/transactions@^5.6.2": +"@ethersproject/transactions@5.6.2", "@ethersproject/transactions@^5.4.0", "@ethersproject/transactions@^5.6.2": version "5.6.2" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.6.2.tgz#793a774c01ced9fe7073985bb95a4b4e57a6370b" integrity sha512-BuV63IRPHmJvthNkkt9G70Ullx6AcM+SDc+a8Aw/8Yew6YwT51TcBKEp1P4oOQ/bP25I18JJr7rcFRgFtU9B2Q== @@ -761,6 +677,18 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@nomicfoundation/hardhat-chai-matchers@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-1.0.3.tgz#a48ed0a3a58de616e4f553368d79c13a8850ebd5" + integrity sha512-qEE7Drs2HSY+krH09TXm6P9LFogs0BqbUq6wPD7nQRhmJ+p5zoDaIZjM5WL1pHqU5MpGqya3y+BdwmTYBfU5UA== + dependencies: + "@ethersproject/abi" "^5.1.2" + "@types/chai-as-promised" "^7.1.3" + chai-as-promised "^7.1.1" + chalk "^2.4.2" + deep-eql "^4.0.1" + ordinal "^1.0.3" + "@nomiclabs/hardhat-ethers@^2.0.3": version "2.1.1" resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.1.1.tgz#3f1d1ab49813d1bae4c035cc1adec224711e528b" @@ -873,43 +801,6 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.3.tgz#939534757a81f8d69cc854c7692805684ff3111e" integrity sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw== -"@resolver-engine/core@^0.3.3": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@resolver-engine/core/-/core-0.3.3.tgz#590f77d85d45bc7ecc4e06c654f41345db6ca967" - integrity sha512-eB8nEbKDJJBi5p5SrvrvILn4a0h42bKtbCTri3ZxCGt6UvoQyp7HnGOfki944bUjBSHKK3RvgfViHn+kqdXtnQ== - dependencies: - debug "^3.1.0" - is-url "^1.2.4" - request "^2.85.0" - -"@resolver-engine/fs@^0.3.3": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@resolver-engine/fs/-/fs-0.3.3.tgz#fbf83fa0c4f60154a82c817d2fe3f3b0c049a973" - integrity sha512-wQ9RhPUcny02Wm0IuJwYMyAG8fXVeKdmhm8xizNByD4ryZlx6PP6kRen+t/haF43cMfmaV7T3Cx6ChOdHEhFUQ== - dependencies: - "@resolver-engine/core" "^0.3.3" - debug "^3.1.0" - -"@resolver-engine/imports-fs@^0.3.3": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@resolver-engine/imports-fs/-/imports-fs-0.3.3.tgz#4085db4b8d3c03feb7a425fbfcf5325c0d1e6c1b" - integrity sha512-7Pjg/ZAZtxpeyCFlZR5zqYkz+Wdo84ugB5LApwriT8XFeQoLwGUj4tZFFvvCuxaNCcqZzCYbonJgmGObYBzyCA== - dependencies: - "@resolver-engine/fs" "^0.3.3" - "@resolver-engine/imports" "^0.3.3" - debug "^3.1.0" - -"@resolver-engine/imports@^0.3.3": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@resolver-engine/imports/-/imports-0.3.3.tgz#badfb513bb3ff3c1ee9fd56073e3144245588bcc" - integrity sha512-anHpS4wN4sRMwsAbMXhMfOD/y4a4Oo0Cw/5+rue7hSwGWsDOQaAU1ClK1OxjUC35/peazxEl8JaSRRS+Xb8t3Q== - dependencies: - "@resolver-engine/core" "^0.3.3" - debug "^3.1.0" - hosted-git-info "^2.6.0" - path-browserify "^1.0.0" - url "^0.11.0" - "@scure/base@~1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" @@ -1000,11 +891,6 @@ "@sentry/types" "5.30.0" tslib "^1.9.3" -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - "@solidity-parser/parser@^0.14.0", "@solidity-parser/parser@^0.14.1", "@solidity-parser/parser@^0.14.2", "@solidity-parser/parser@^0.14.3": version "0.14.3" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.3.tgz#0d627427b35a40d8521aaa933cc3df7d07bfa36f" @@ -1012,13 +898,6 @@ dependencies: antlr4ts "^0.5.0-alpha.4" -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -1039,13 +918,6 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== -"@typechain/ethers-v5@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-2.0.0.tgz#cd3ca1590240d587ca301f4c029b67bfccd08810" - integrity sha512-0xdCkyGOzdqh4h5JSf+zoWx85IusEjDcPIwNEHP8mrWSnCae4rvrqB+/gtpdNfX7zjlFlZiMeePn2r63EI3Lrw== - dependencies: - ethers "^5.0.2" - "@typechain/ethers-v5@^8.0.5": version "8.0.5" resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-8.0.5.tgz#d469420e9a73deb7fa076cde9edb45d713dd1b8c" @@ -1073,13 +945,20 @@ dependencies: "@types/node" "*" -"@types/bn.js@^4.11.3", "@types/bn.js@^4.11.5": +"@types/bn.js@^4.11.3": version "4.11.6" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg== dependencies: "@types/node" "*" +"@types/chai-as-promised@^7.1.3": + version "7.1.5" + resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz#6e016811f6c7a64f2eed823191c3a6955094e255" + integrity sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ== + dependencies: + "@types/chai" "*" + "@types/chai@*", "@types/chai@^4.3.0": version "4.3.3" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.3.tgz#3c90752792660c4b562ad73b3fbd68bf3bc7ae07" @@ -1118,26 +997,11 @@ resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.1.tgz#c48c2e27b65d2a153b19bfc1a317e30872e01eef" integrity sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw== -"@types/mkdirp@^0.5.2": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f" - integrity sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg== - dependencies: - "@types/node" "*" - "@types/mocha@^9.0.0": version "9.1.1" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== -"@types/node-fetch@^2.5.5": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" - integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A== - dependencies: - "@types/node" "*" - form-data "^3.0.0" - "@types/node@*": version "18.7.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.6.tgz#31743bc5772b6ac223845e18c3fc26f042713c83" @@ -1148,11 +1012,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== -"@types/node@^12.12.6": - version "12.20.55" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" - integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== - "@types/node@^17.0.0": version "17.0.45" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" @@ -1180,13 +1039,6 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== -"@types/resolve@^0.0.8": - version "0.0.8" - resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" - integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== - dependencies: - "@types/node" "*" - "@types/secp256k1@^4.0.1": version "4.0.3" resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.3.tgz#1b8e55d8e00f08ee7220b4d59a6abe89c37a901c" @@ -1232,11 +1084,6 @@ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== -"@yarnpkg/lockfile@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" - integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== - abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -1244,27 +1091,6 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" -abstract-leveldown@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-3.0.0.tgz#5cb89f958a44f526779d740d1440e743e0c30a57" - integrity sha512-KUWx9UWGQD12zsmLNj64/pndaz4iJh/Pj7nopgkfDG6RlCcbMZvT6+9l7dchK4idog2Is8VdC/PvNbFuFmalIQ== - dependencies: - xtend "~4.0.0" - -abstract-leveldown@^2.4.1, abstract-leveldown@~2.7.1: - version "2.7.2" - resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-2.7.2.tgz#87a44d7ebebc341d59665204834c8b7e0932cc93" - integrity sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w== - dependencies: - xtend "~4.0.0" - -abstract-leveldown@^5.0.0, abstract-leveldown@~5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-5.0.0.tgz#f7128e1f86ccabf7d2893077ce5d06d798e386c6" - integrity sha512-5mU5P1gXtsMIXg65/rsYGsi93+MlogXZ9FA8JnwKurHQg64bfXwGYVdVdijNTVNOlAsuIiOwHdvFFD5JqCJQ7A== - dependencies: - xtend "~4.0.0" - abstract-leveldown@^6.2.1: version "6.3.0" resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-6.3.0.tgz#d25221d1e6612f820c35963ba4bd739928f6026a" @@ -1276,13 +1102,6 @@ abstract-leveldown@^6.2.1: level-supports "~1.0.0" xtend "~4.0.0" -abstract-leveldown@~2.6.0: - version "2.6.3" - resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz#1c5e8c6a5ef965ae8c35dfb3a8770c476b82c4b8" - integrity sha512-2++wDf/DYqkPR3o5tbfdhF96EfMApo1GpPfzOsR/ZYXdkSmELlvOOEAl9iKkRsktMPHdGjO4rtkBpf2I7TiTeA== - dependencies: - xtend "~4.0.0" - abstract-leveldown@~6.2.1: version "6.2.3" resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz#036543d87e3710f2528e47040bc3261b77a9a8eb" @@ -1294,14 +1113,6 @@ abstract-leveldown@~6.2.1: level-supports "~1.0.0" xtend "~4.0.0" -accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - acorn-jsx@^5.0.0: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -1332,11 +1143,6 @@ aes-js@3.0.0: resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== -aes-js@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a" - integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ== - agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -1389,11 +1195,6 @@ ansi-escapes@^4.3.0: dependencies: type-fest "^0.21.3" -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== - ansi-regex@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" @@ -1414,11 +1215,6 @@ ansi-regex@^6.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== - ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -1473,35 +1269,6 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== - -array-back@^1.0.3, array-back@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/array-back/-/array-back-1.0.4.tgz#644ba7f095f7ffcf7c43b5f0dc39d3c1f03c063b" - integrity sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw== - dependencies: - typical "^2.6.0" - -array-back@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/array-back/-/array-back-2.0.0.tgz#6877471d51ecc9c9bfa6136fb6c7d5fe69748022" - integrity sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw== - dependencies: - typical "^2.6.1" - array-back@^3.0.1, array-back@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" @@ -1512,11 +1279,6 @@ array-back@^4.0.1, array-back@^4.0.2: resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -1527,11 +1289,6 @@ array-uniq@1.0.3: resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== - array.prototype.reduce@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz#8167e80089f78bff70a99e20bd4201d4663b0a6f" @@ -1548,16 +1305,6 @@ asap@~2.0.6: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== -asn1.js@^5.2.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" - integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - safer-buffer "^2.1.0" - asn1@~0.2.3: version "0.2.6" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" @@ -1575,11 +1322,6 @@ assertion-error@^1.1.0: resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== - ast-parents@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/ast-parents/-/ast-parents-0.0.1.tgz#508fd0f05d0c48775d9eccda2e174423261e8dd3" @@ -1595,31 +1337,14 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== -async-eventemitter@^0.2.2, async-eventemitter@^0.2.4: +async-eventemitter@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/async-eventemitter/-/async-eventemitter-0.2.4.tgz#f5e7c8ca7d3e46aab9ec40a292baf686a0bafaca" integrity sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw== dependencies: async "^2.4.0" -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - -async@2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" - integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg== - dependencies: - lodash "^4.17.11" - -async@^1.4.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - integrity sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w== - -async@^2.0.1, async@^2.1.2, async@^2.4.0, async@^2.5.0, async@^2.6.1: +async@^2.4.0: version "2.6.4" resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== @@ -1636,11 +1361,6 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -1658,538 +1378,12 @@ axios@^0.21.1: dependencies: follow-redirects "^1.14.0" -babel-code-frame@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - integrity sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g== - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - -babel-core@^6.0.14, babel-core@^6.26.0: - version "6.26.3" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" - integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA== - dependencies: - babel-code-frame "^6.26.0" - babel-generator "^6.26.0" - babel-helpers "^6.24.1" - babel-messages "^6.23.0" - babel-register "^6.26.0" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - convert-source-map "^1.5.1" - debug "^2.6.9" - json5 "^0.5.1" - lodash "^4.17.4" - minimatch "^3.0.4" - path-is-absolute "^1.0.1" - private "^0.1.8" - slash "^1.0.0" - source-map "^0.5.7" - -babel-generator@^6.26.0: - version "6.26.1" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" - integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== - dependencies: - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - detect-indent "^4.0.0" - jsesc "^1.3.0" - lodash "^4.17.4" - source-map "^0.5.7" - trim-right "^1.0.1" - -babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" - integrity sha512-gCtfYORSG1fUMX4kKraymq607FWgMWg+j42IFPc18kFQEsmtaibP4UrqsXt8FlEJle25HUd4tsoDR7H2wDhe9Q== - dependencies: - babel-helper-explode-assignable-expression "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-call-delegate@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" - integrity sha512-RL8n2NiEj+kKztlrVJM9JT1cXzzAdvWFh76xh/H1I4nKwunzE4INBXn8ieCZ+wh4zWszZk7NBS1s/8HR5jDkzQ== - dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-define-map@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" - integrity sha512-bHkmjcC9lM1kmZcVpA5t2om2nzT/xiZpo6TJq7UlZ3wqKfzia4veeXbIhKvJXAMzhhEBd3cR1IElL5AenWEUpA== - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-helper-explode-assignable-expression@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" - integrity sha512-qe5csbhbvq6ccry9G7tkXbzNtcDiH4r51rrPUbwwoTzZ18AqxWYRZT6AOmxrpxKnQBW0pYlBI/8vh73Z//78nQ== - dependencies: - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-function-name@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" - integrity sha512-Oo6+e2iX+o9eVvJ9Y5eKL5iryeRdsIkwRYheCuhYdVHsdEQysbc2z2QkqCLIYnNxkT5Ss3ggrHdXiDI7Dhrn4Q== - dependencies: - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-get-function-arity@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" - integrity sha512-WfgKFX6swFB1jS2vo+DwivRN4NB8XUdM3ij0Y1gnC21y1tdBoe6xjVnd7NSI6alv+gZXCtJqvrTeMW3fR/c0ng== - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-hoist-variables@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" - integrity sha512-zAYl3tqerLItvG5cKYw7f1SpvIxS9zi7ohyGHaI9cgDUjAT6YcY9jIEH5CstetP5wHIVSceXwNS7Z5BpJg+rOw== - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-optimise-call-expression@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" - integrity sha512-Op9IhEaxhbRT8MDXx2iNuMgciu2V8lDvYCNQbDGjdBNCjaMvyLf4wl4A3b8IgndCyQF8TwfgsQ8T3VD8aX1/pA== - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-regex@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" - integrity sha512-VlPiWmqmGJp0x0oK27Out1D+71nVVCTSdlbhIVoaBAj2lUgrNjBCRR9+llO4lTSb2O4r7PJg+RobRkhBrf6ofg== - dependencies: - babel-runtime "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-helper-remap-async-to-generator@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" - integrity sha512-RYqaPD0mQyQIFRu7Ho5wE2yvA/5jxqCIj/Lv4BXNq23mHYu/vxikOy2JueLiBxQknwapwrJeNCesvY0ZcfnlHg== - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-replace-supers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" - integrity sha512-sLI+u7sXJh6+ToqDr57Bv973kCepItDhMou0xCP2YPVmR1jkHSCY+p1no8xErbV1Siz5QE8qKT1WIwybSWlqjw== - dependencies: - babel-helper-optimise-call-expression "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helpers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" - integrity sha512-n7pFrqQm44TCYvrCDb0MqabAF+JUBq+ijBvNMUxpkLjJaAu32faIexewMumrH5KLLJ1HDyT0PTEqRyAe/GwwuQ== - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-messages@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" - integrity sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w== - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-check-es2015-constants@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" - integrity sha512-B1M5KBP29248dViEo1owyY32lk1ZSH2DaNNrXLGt8lyjjHm7pBqAdQ7VKUPR6EEDO323+OvT3MQXbCin8ooWdA== - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-syntax-async-functions@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" - integrity sha512-4Zp4unmHgw30A1eWI5EpACji2qMocisdXhAftfhXoSV9j0Tvj6nRFE3tOmRY912E0FMRm/L5xWE7MGVT2FoLnw== - -babel-plugin-syntax-exponentiation-operator@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" - integrity sha512-Z/flU+T9ta0aIEKl1tGEmN/pZiI1uXmCiGFRegKacQfEJzp7iNsKloZmyJlQr+75FCJtiFfGIK03SiCvCt9cPQ== - -babel-plugin-syntax-trailing-function-commas@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" - integrity sha512-Gx9CH3Q/3GKbhs07Bszw5fPTlU+ygrOGfAhEt7W2JICwufpC4SuO0mG0+4NykPBSYPMJhqvVlDBU17qB1D+hMQ== - -babel-plugin-transform-async-to-generator@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" - integrity sha512-7BgYJujNCg0Ti3x0c/DL3tStvnKS6ktIYOmo9wginv/dfZOrbSZ+qG4IRRHMBOzZ5Awb1skTiAsQXg/+IWkZYw== - dependencies: - babel-helper-remap-async-to-generator "^6.24.1" - babel-plugin-syntax-async-functions "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-arrow-functions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" - integrity sha512-PCqwwzODXW7JMrzu+yZIaYbPQSKjDTAsNNlK2l5Gg9g4rz2VzLnZsStvp/3c46GfXpwkyufb3NCyG9+50FF1Vg== - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" - integrity sha512-2+ujAT2UMBzYFm7tidUsYh+ZoIutxJ3pN9IYrF1/H6dCKtECfhmB8UkHVpyxDwkj0CYbQG35ykoz925TUnBc3A== - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoping@^6.23.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" - integrity sha512-YiN6sFAQ5lML8JjCmr7uerS5Yc/EMbgg9G8ZNmk2E3nYX4ckHR01wrkeeMijEf5WHNK5TW0Sl0Uu3pv3EdOJWw== - dependencies: - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-plugin-transform-es2015-classes@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" - integrity sha512-5Dy7ZbRinGrNtmWpquZKZ3EGY8sDgIVB4CU8Om8q8tnMLrD/m94cKglVcHps0BCTdZ0TJeeAWOq2TK9MIY6cag== - dependencies: - babel-helper-define-map "^6.24.1" - babel-helper-function-name "^6.24.1" - babel-helper-optimise-call-expression "^6.24.1" - babel-helper-replace-supers "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-computed-properties@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" - integrity sha512-C/uAv4ktFP/Hmh01gMTvYvICrKze0XVX9f2PdIXuriCSvUmV9j+u+BB9f5fJK3+878yMK6dkdcq+Ymr9mrcLzw== - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-destructuring@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" - integrity sha512-aNv/GDAW0j/f4Uy1OEPZn1mqD+Nfy9viFGBfQ5bZyT35YqOiqx7/tXdyfZkJ1sC21NyEsBdfDY6PYmLHF4r5iA== - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-duplicate-keys@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" - integrity sha512-ossocTuPOssfxO2h+Z3/Ea1Vo1wWx31Uqy9vIiJusOP4TbF7tPs9U0sJ9pX9OJPf4lXRGj5+6Gkl/HHKiAP5ug== - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-for-of@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" - integrity sha512-DLuRwoygCoXx+YfxHLkVx5/NpeSbVwfoTeBykpJK7JhYWlL/O8hgAK/reforUnZDlxasOrVPPJVI/guE3dCwkw== - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-function-name@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" - integrity sha512-iFp5KIcorf11iBqu/y/a7DK3MN5di3pNCzto61FqCNnUX4qeBwcV1SLqe10oXNnCaxBUImX3SckX2/o1nsrTcg== - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-literals@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" - integrity sha512-tjFl0cwMPpDYyoqYA9li1/7mGFit39XiNX5DKC/uCNjBctMxyL1/PT/l4rSlbvBG1pOKI88STRdUsWXB3/Q9hQ== - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" - integrity sha512-LnIIdGWIKdw7zwckqx+eGjcS8/cl8D74A3BpJbGjKTFFNJSMrjN4bIh22HY1AlkUbeLG6X6OZj56BDvWD+OeFA== - dependencies: - babel-plugin-transform-es2015-modules-commonjs "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: - version "6.26.2" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" - integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q== - dependencies: - babel-plugin-transform-strict-mode "^6.24.1" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-types "^6.26.0" - -babel-plugin-transform-es2015-modules-systemjs@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" - integrity sha512-ONFIPsq8y4bls5PPsAWYXH/21Hqv64TBxdje0FvU3MhIV6QM2j5YS7KvAzg/nTIVLot2D2fmFQrFWCbgHlFEjg== - dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-modules-umd@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" - integrity sha512-LpVbiT9CLsuAIp3IG0tfbVo81QIhn6pE8xBJ7XSeCtFlMltuar5VuBV6y6Q45tpui9QWcy5i0vLQfCfrnF7Kiw== - dependencies: - babel-plugin-transform-es2015-modules-amd "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-object-super@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" - integrity sha512-8G5hpZMecb53vpD3mjs64NhI1au24TAmokQ4B+TBFBjN9cVoGoOvotdrMMRmHvVZUEvqGUPWL514woru1ChZMA== - dependencies: - babel-helper-replace-supers "^6.24.1" - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-parameters@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" - integrity sha512-8HxlW+BB5HqniD+nLkQ4xSAVq3bR/pcYW9IigY+2y0dI+Y7INFeTbfAQr+63T3E4UDsZGjyb+l9txUnABWxlOQ== - dependencies: - babel-helper-call-delegate "^6.24.1" - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-shorthand-properties@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" - integrity sha512-mDdocSfUVm1/7Jw/FIRNw9vPrBQNePy6wZJlR8HAUBLybNp1w/6lr6zZ2pjMShee65t/ybR5pT8ulkLzD1xwiw== - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-spread@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" - integrity sha512-3Ghhi26r4l3d0Js933E5+IhHwk0A1yiutj9gwvzmFbVV0sPMYk2lekhOufHBswX7NCoSeF4Xrl3sCIuSIa+zOg== - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-sticky-regex@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" - integrity sha512-CYP359ADryTo3pCsH0oxRo/0yn6UsEZLqYohHmvLQdfS9xkf+MbCzE3/Kolw9OYIY4ZMilH25z/5CbQbwDD+lQ== - dependencies: - babel-helper-regex "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-template-literals@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" - integrity sha512-x8b9W0ngnKzDMHimVtTfn5ryimars1ByTqsfBDwAqLibmuuQY6pgBQi5z1ErIsUOWBdw1bW9FSz5RZUojM4apg== - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-typeof-symbol@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" - integrity sha512-fz6J2Sf4gYN6gWgRZaoFXmq93X+Li/8vf+fb0sGDVtdeWvxC9y5/bTD7bvfWMEq6zetGEHpWjtzRGSugt5kNqw== - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-unicode-regex@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" - integrity sha512-v61Dbbihf5XxnYjtBN04B/JBvsScY37R1cZT5r9permN1cp+b70DY3Ib3fIkgn1DI9U3tGgBJZVD8p/mE/4JbQ== - dependencies: - babel-helper-regex "^6.24.1" - babel-runtime "^6.22.0" - regexpu-core "^2.0.0" - -babel-plugin-transform-exponentiation-operator@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" - integrity sha512-LzXDmbMkklvNhprr20//RStKVcT8Cu+SQtX18eMHLhjHf2yFzwtQ0S2f0jQ+89rokoNdmwoSqYzAhq86FxlLSQ== - dependencies: - babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" - babel-plugin-syntax-exponentiation-operator "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-regenerator@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" - integrity sha512-LS+dBkUGlNR15/5WHKe/8Neawx663qttS6AGqoOUhICc9d1KciBvtrQSuc0PI+CxQ2Q/S1aKuJ+u64GtLdcEZg== - dependencies: - regenerator-transform "^0.10.0" - -babel-plugin-transform-strict-mode@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" - integrity sha512-j3KtSpjyLSJxNoCDrhwiJad8kw0gJ9REGj8/CqL0HeRyLnvUNYV9zcqluL6QJSXh3nfsLEmSLvwRfGzrgR96Pw== - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-preset-env@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a" - integrity sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg== - dependencies: - babel-plugin-check-es2015-constants "^6.22.0" - babel-plugin-syntax-trailing-function-commas "^6.22.0" - babel-plugin-transform-async-to-generator "^6.22.0" - babel-plugin-transform-es2015-arrow-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoping "^6.23.0" - babel-plugin-transform-es2015-classes "^6.23.0" - babel-plugin-transform-es2015-computed-properties "^6.22.0" - babel-plugin-transform-es2015-destructuring "^6.23.0" - babel-plugin-transform-es2015-duplicate-keys "^6.22.0" - babel-plugin-transform-es2015-for-of "^6.23.0" - babel-plugin-transform-es2015-function-name "^6.22.0" - babel-plugin-transform-es2015-literals "^6.22.0" - babel-plugin-transform-es2015-modules-amd "^6.22.0" - babel-plugin-transform-es2015-modules-commonjs "^6.23.0" - babel-plugin-transform-es2015-modules-systemjs "^6.23.0" - babel-plugin-transform-es2015-modules-umd "^6.23.0" - babel-plugin-transform-es2015-object-super "^6.22.0" - babel-plugin-transform-es2015-parameters "^6.23.0" - babel-plugin-transform-es2015-shorthand-properties "^6.22.0" - babel-plugin-transform-es2015-spread "^6.22.0" - babel-plugin-transform-es2015-sticky-regex "^6.22.0" - babel-plugin-transform-es2015-template-literals "^6.22.0" - babel-plugin-transform-es2015-typeof-symbol "^6.23.0" - babel-plugin-transform-es2015-unicode-regex "^6.22.0" - babel-plugin-transform-exponentiation-operator "^6.22.0" - babel-plugin-transform-regenerator "^6.22.0" - browserslist "^3.2.6" - invariant "^2.2.2" - semver "^5.3.0" - -babel-register@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" - integrity sha512-veliHlHX06wjaeY8xNITbveXSiI+ASFnOqvne/LaIJIqOWi2Ogmj91KOugEz/hoh/fwMhXNBJPCv8Xaz5CyM4A== - dependencies: - babel-core "^6.26.0" - babel-runtime "^6.26.0" - core-js "^2.5.0" - home-or-tmp "^2.0.0" - lodash "^4.17.4" - mkdirp "^0.5.1" - source-map-support "^0.4.15" - -babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g== - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - -babel-template@^6.24.1, babel-template@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" - integrity sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg== - dependencies: - babel-runtime "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - lodash "^4.17.4" - -babel-traverse@^6.24.1, babel-traverse@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" - integrity sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA== - dependencies: - babel-code-frame "^6.26.0" - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - debug "^2.6.8" - globals "^9.18.0" - invariant "^2.2.2" - lodash "^4.17.4" - -babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" - integrity sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g== - dependencies: - babel-runtime "^6.26.0" - esutils "^2.0.2" - lodash "^4.17.4" - to-fast-properties "^1.0.3" - -babelify@^7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/babelify/-/babelify-7.3.0.tgz#aa56aede7067fd7bd549666ee16dc285087e88e5" - integrity sha512-vID8Fz6pPN5pJMdlUnNFSfrlcx5MUule4k9aKs/zbZPyXxMTcRrB0M4Tarw22L8afr8eYSWxDPYCob3TdrqtlA== - dependencies: - babel-core "^6.0.14" - object-assign "^4.0.0" - -babylon@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" - integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== - -backoff@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/backoff/-/backoff-2.5.0.tgz#f616eda9d3e4b66b8ca7fca79f695722c5f8e26f" - integrity sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA== - dependencies: - precond "0.2" - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base-x@^3.0.2, base-x@^3.0.8: +base-x@^3.0.2: version "3.0.9" resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== @@ -2201,19 +1395,6 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -2226,70 +1407,26 @@ bech32@1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -bignumber.js@^9.0.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.0.tgz#8d340146107fe3a6cb8d40699643c302e8773b62" - integrity sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A== - binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bip39@2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/bip39/-/bip39-2.5.0.tgz#51cbd5179460504a63ea3c000db3f787ca051235" - integrity sha512-xwIx/8JKoT2+IPJpFEfXoWdYwP7UVAoUxxLNfGCfVowaJE7yg1Y5B1BVPqlUNsBq5/nGwmFkwRJ8xDW4sX8OdA== - dependencies: - create-hash "^1.1.0" - pbkdf2 "^3.0.9" - randombytes "^2.0.1" - safe-buffer "^5.0.1" - unorm "^1.3.3" - blakejs@^1.1.0: version "1.2.1" resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== -bluebird@^3.5.0, bluebird@^3.5.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - -bn.js@4.11.6: - version "4.11.6" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" - integrity sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA== - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.8.0: +bn.js@^4.0.0, bn.js@^4.11.0, bn.js@^4.11.8, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: +bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== -body-parser@1.20.0, body-parser@^1.16.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" - integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.10.3" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -2305,22 +1442,6 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -2338,7 +1459,7 @@ browser-stdout@1.3.1: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.2.0: +browserify-aes@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== @@ -2350,56 +1471,6 @@ browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.2.0: inherits "^2.0.1" safe-buffer "^5.0.1" -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" - integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" - integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== - dependencies: - bn.js "^5.0.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" - integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== - dependencies: - bn.js "^5.1.1" - browserify-rsa "^4.0.1" - create-hash "^1.2.0" - create-hmac "^1.1.7" - elliptic "^6.5.3" - inherits "^2.0.4" - parse-asn1 "^5.1.5" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -browserslist@^3.2.6: - version "3.2.8" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6" - integrity sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ== - dependencies: - caniuse-lite "^1.0.30000844" - electron-to-chromium "^1.3.47" - bs58@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -2421,11 +1492,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer-to-arraybuffer@^0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz#6064a40fa76eb43c723aba9ef8f6e1216d10511a" - integrity sha512-3dthu5CYiVB1DEJp61FtApNnNndTckcqe4pFcLdvHtrpG+kcyekCJKg4MRiDcFW7A6AODnXB9U4dwQiCW5kzJQ== - buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -2438,7 +1504,7 @@ buffer-xor@^2.0.1: dependencies: safe-buffer "^5.1.1" -buffer@^5.0.5, buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0: +buffer@^5.5.0, buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -2446,70 +1512,12 @@ buffer@^5.0.5, buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0: base64-js "^1.3.1" ieee754 "^1.1.13" -bufferutil@^4.0.1: - version "4.0.6" - resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.6.tgz#ebd6c67c7922a0e902f053e5d8be5ec850e48433" - integrity sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw== - dependencies: - node-gyp-build "^4.3.0" - bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -bytewise-core@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/bytewise-core/-/bytewise-core-1.2.3.tgz#3fb410c7e91558eb1ab22a82834577aa6bd61d42" - integrity sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA== - dependencies: - typewise-core "^1.2" - -bytewise@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/bytewise/-/bytewise-1.1.0.tgz#1d13cbff717ae7158094aa881b35d081b387253e" - integrity sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ== - dependencies: - bytewise-core "^1.2.2" - typewise "^1.0.3" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - -cachedown@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cachedown/-/cachedown-1.0.0.tgz#d43f036e4510696b31246d7db31ebf0f7ac32d15" - integrity sha512-t+yVk82vQWCJF3PsWHMld+jhhjkkWjcAzz8NbFx1iULOXWl8Tm/FdM4smZNVw3MRr0X+lVTx9PKzvEn4Ng19RQ== - dependencies: - abstract-leveldown "^2.4.1" - lru-cache "^3.2.0" - -call-bind@^1.0.0, call-bind@^1.0.2, call-bind@~1.0.2: +call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== @@ -2541,11 +1549,6 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camelcase@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" - integrity sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg== - camelcase@^5.0.0: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -2556,16 +1559,18 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30000844: - version "1.0.30001378" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001378.tgz#3d2159bf5a8f9ca093275b0d3ecc717b00f27b67" - integrity sha512-JVQnfoO7FK7WvU4ZkBRbPjaot4+YqxogSDosHv0Hv5mWpUESmN+UubMU6L/hGz8QlQ2aY5U0vR6MOs6j/CXpNA== - caseless@^0.12.0, caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== +chai-as-promised@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" + integrity sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA== + dependencies: + check-error "^1.0.2" + chai@^4.3.4: version "4.3.6" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" @@ -2579,18 +1584,7 @@ chai@^4.3.4: pathval "^1.1.1" type-detect "^4.0.5" -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.1.0, 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== @@ -2622,13 +1616,6 @@ check-error@^1.0.2: resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== -checkpoint-store@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/checkpoint-store/-/checkpoint-store-1.1.0.tgz#04e4cb516b91433893581e6d4601a78e9552ea06" - integrity sha512-J/NdY2WvIx654cc6LWSq/IYFFCUf75fFTgwzFnmbqyORH4MwgiQCgswLLKBGzmsyTI5V7i5bp/So6sMbDWhedg== - dependencies: - functional-red-black-tree "^1.0.1" - chokidar@3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" @@ -2659,27 +1646,11 @@ chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" -chownr@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -cids@^0.7.1: - version "0.7.5" - resolved "https://registry.yarnpkg.com/cids/-/cids-0.7.5.tgz#60a08138a99bfb69b6be4ceb63bfef7a396b28b2" - integrity sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA== - dependencies: - buffer "^5.5.0" - class-is "^1.1.0" - multibase "~0.6.0" - multicodec "^1.0.0" - multihashes "~0.4.15" - cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -2688,21 +1659,6 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -class-is@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/class-is/-/class-is-1.1.0.tgz#9d3c0fba0440d211d843cec3dedfa48055005825" - integrity sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw== - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -2760,15 +1716,6 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== -cliui@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - integrity sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w== - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" - cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -2787,31 +1734,6 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" -clone-response@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" - integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== - dependencies: - mimic-response "^1.0.0" - -clone@2.1.2, clone@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" - integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA== - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw== - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -2858,15 +1780,6 @@ command-exists@^1.2.8: resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== -command-line-args@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-4.0.7.tgz#f8d1916ecb90e9e121eda6428e41300bfb64cc46" - integrity sha512-aUdPvQRAyBvQd2n7jXcsMDz68ckBJELXNzBybCHOibUWEg0mWTnaYCSRU8h9R+aNRSvDihJtssSRCiDRpLaezA== - dependencies: - array-back "^2.0.0" - find-replace "^1.0.3" - typical "^2.6.1" - command-line-args@^5.1.1: version "5.2.1" resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e" @@ -2907,17 +1820,12 @@ commander@^9.3.0: resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.0.tgz#bc4a40918fefe52e22450c111ecd6b7acce6f11c" integrity sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw== -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concat-stream@^1.5.1, concat-stream@^1.6.0, concat-stream@^1.6.2: +concat-stream@^1.6.0, concat-stream@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -2927,69 +1835,16 @@ concat-stream@^1.5.1, concat-stream@^1.6.0, concat-stream@^1.6.2: readable-stream "^2.2.2" typedarray "^0.0.6" -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-hash@^2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/content-hash/-/content-hash-2.5.2.tgz#bbc2655e7c21f14fd3bfc7b7d4bfe6e454c9e211" - integrity sha512-FvIQKy0S1JaWV10sMsA7TRx8bpU+pqPkhbsfvOJAdjRXvYxEckAwQWGwtRjiaJfh+E0DvcWUGqcdjwMGFjsSdw== - dependencies: - cids "^0.7.1" - multicodec "^0.5.5" - multihashes "^0.4.15" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -convert-source-map@^1.5.1: - version "1.8.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== - dependencies: - safe-buffer "~5.1.1" - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - cookie@^0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== -cookiejar@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc" - integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ== - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== - core-js-pure@^3.0.1: version "3.24.1" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.24.1.tgz#8839dde5da545521bf282feb7dc6d0b425f39fd3" integrity sha512-r1nJk41QLLPyozHUUPmILCEMtMw24NG4oWK6RbsDdjzQgg9ZvrUsPBj1MnG0wXXp1DCDU6j+wUvEmBSrtRbLXg== -core-js@^2.4.0, core-js@^2.5.0: - version "2.6.12" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" - integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== - core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -3000,14 +1855,6 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cors@^2.8.1: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - cosmiconfig@^5.0.7: version "5.2.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" @@ -3023,14 +1870,6 @@ crc-32@^1.2.0: resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== -create-ecdh@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" - integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== - dependencies: - bn.js "^4.1.0" - elliptic "^6.5.3" - create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" @@ -3042,7 +1881,7 @@ create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: ripemd160 "^2.0.1" sha.js "^2.4.0" -create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: +create-hmac@^1.1.4, create-hmac@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== @@ -3059,14 +1898,6 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-fetch@^2.1.0, cross-fetch@^2.1.1: - version "2.2.6" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.2.6.tgz#2ef0bb39a24ac034787965c457368a28730e220a" - integrity sha512-9JZz+vXCmfKUZ68zAptS7k4Nu8e2qcibe7WVZYps7sAgk5R8GYTc+T1WR0v1rlP9HxgARmOX1UTIJZFytajpNA== - dependencies: - node-fetch "^2.6.7" - whatwg-fetch "^2.0.4" - cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -3092,31 +1923,6 @@ cross-spawn@^7.0.3: resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== -crypto-browserify@3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - -d@1, d@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" - integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== - dependencies: - es5-ext "^0.10.50" - type "^1.0.1" - dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -3124,13 +1930,6 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - debug@3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -3145,14 +1944,7 @@ debug@4, debug@4.3.4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, de dependencies: ms "2.1.2" -debug@^3.1.0: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -decamelize@^1.1.1, decamelize@^1.2.0: +decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== @@ -3162,18 +1954,6 @@ decamelize@^4.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== - -decompress-response@^3.2.0, decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== - dependencies: - mimic-response "^1.0.0" - deep-eql@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" @@ -3181,17 +1961,12 @@ deep-eql@^3.0.1: dependencies: type-detect "^4.0.0" -deep-equal@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" - integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== +deep-eql@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.0.tgz#67f2078a06d899d9d954762ef61358f2eef00507" + integrity sha512-4YM7QHOMBoVWqGPnp3OPPK7+WCIhUR2OTpahlNQFiyTH3QEeiu9MtBiTAJBkfny4PNhpFbV/jm3lv0iCfb40MA== dependencies: - is-arguments "^1.0.4" - is-date-object "^1.0.1" - is-regex "^1.0.4" - object-is "^1.0.1" - object-keys "^1.1.1" - regexp.prototype.flags "^1.2.0" + type-detect "^4.0.0" deep-extend@~0.6.0: version "0.6.0" @@ -3203,26 +1978,6 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== - -deferred-leveldown@~1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/deferred-leveldown/-/deferred-leveldown-1.2.2.tgz#3acd2e0b75d1669924bc0a4b642851131173e1eb" - integrity sha512-uukrWD2bguRtXilKt6cAWKyoXrTSMo5m7crUdLfWQmu8kIm88w3QZoUL+6nhpfKVmhHANER6Re3sKoNoZ3IKMA== - dependencies: - abstract-leveldown "~2.6.0" - -deferred-leveldown@~4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/deferred-leveldown/-/deferred-leveldown-4.0.2.tgz#0b0570087827bf480a23494b398f04c128c19a20" - integrity sha512-5fMC8ek8alH16QiV0lTCis610D1Zt1+LA4MS4d63JgS32lrCjTFDUFz2ao09/j2I4Bqb5jL4FZYwu7Jz0XO1ww== - dependencies: - abstract-leveldown "~5.0.0" - inherits "^2.0.3" - deferred-leveldown@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz#27a997ad95408b61161aa69bd489b86c71b78058" @@ -3239,33 +1994,6 @@ define-properties@^1.1.2, define-properties@^1.1.3, define-properties@^1.1.4: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA== - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA== - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -defined@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - integrity sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ== - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -3276,26 +2004,6 @@ depd@2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -des.js@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" - integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-indent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" - integrity sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A== - dependencies: - repeating "^2.0.0" - diff@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -3311,15 +2019,6 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -3334,28 +2033,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-walk@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" - integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== - dotenv@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== -dotignore@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/dotignore/-/dotignore-0.1.2.tgz#f942f2200d28c3a76fbdd6f0ee9f3257c8a2e905" - integrity sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw== - dependencies: - minimatch "^3.0.4" - -duplexer3@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e" - integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== - eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -3369,17 +2051,7 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.3.47: - version "1.4.224" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.224.tgz#ecf2eed395cfedcbbe634658ccc4b457f7b254c3" - integrity sha512-dOujC5Yzj0nOVE23iD5HKqrRSDj2SD7RazpZS/b/WX85MtO6/LzKDF4TlYZTBteB+7fvSg5JpWh0sN7fImNF8w== - -elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4: +elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -3417,22 +2089,6 @@ encode-utf8@^1.0.2: resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -encoding-down@5.0.4, encoding-down@~5.0.0: - version "5.0.4" - resolved "https://registry.yarnpkg.com/encoding-down/-/encoding-down-5.0.4.tgz#1e477da8e9e9d0f7c8293d320044f8b2cd8e9614" - integrity sha512-8CIZLDcSKxgzT+zX8ZVfgNbu8Md2wq/iqa1Y7zyVR18QBEAc0Nmzuvj/N5ykSKpfGzjM8qxbaFntLPwnVoUhZw== - dependencies: - abstract-leveldown "^5.0.0" - inherits "^2.0.3" - level-codec "^9.0.0" - level-errors "^2.0.0" - xtend "^4.0.1" - encoding-down@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/encoding-down/-/encoding-down-6.3.0.tgz#b1c4eb0e1728c146ecaef8e32963c549e76d082b" @@ -3443,20 +2099,6 @@ encoding-down@^6.3.0: level-codec "^9.0.0" level-errors "^2.0.0" -encoding@^0.1.11: - version "0.1.13" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" - integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== - dependencies: - iconv-lite "^0.6.2" - -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - enquirer@^2.3.0, enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -3476,7 +2118,7 @@ errno@~0.1.1: dependencies: prr "~1.0.1" -error-ex@^1.2.0, error-ex@^1.3.1: +error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== @@ -3526,43 +2168,12 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.50: - version "0.10.62" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" - integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== - dependencies: - es6-iterator "^2.0.3" - es6-symbol "^3.1.3" - next-tick "^1.1.0" - -es6-iterator@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-symbol@^3.1.1, es6-symbol@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" - integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== - dependencies: - d "^1.0.1" - ext "^1.1.2" - escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== @@ -3677,32 +2288,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -eth-block-tracker@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/eth-block-tracker/-/eth-block-tracker-3.0.1.tgz#95cd5e763c7293e0b1b2790a2a39ac2ac188a5e1" - integrity sha512-WUVxWLuhMmsfenfZvFO5sbl1qFY2IqUlw/FPVmjjdElpqLsZtSG+wPe9Dz7W/sB6e80HgFKknOmKk2eNlznHug== - dependencies: - eth-query "^2.1.0" - ethereumjs-tx "^1.3.3" - ethereumjs-util "^5.1.3" - ethjs-util "^0.1.3" - json-rpc-engine "^3.6.0" - pify "^2.3.0" - tape "^4.6.3" - -eth-ens-namehash@2.0.8, eth-ens-namehash@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz#229ac46eca86d52e0c991e7cb2aef83ff0f68bcf" - integrity sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw== - dependencies: - idna-uts46-hx "^2.3.1" - js-sha3 "^0.5.7" - eth-gas-reporter@^0.2.24: version "0.2.25" resolved "https://registry.yarnpkg.com/eth-gas-reporter/-/eth-gas-reporter-0.2.25.tgz#546dfa946c1acee93cb1a94c2a1162292d6ff566" @@ -3724,127 +2309,6 @@ eth-gas-reporter@^0.2.24: sha1 "^1.1.1" sync-request "^6.0.0" -eth-json-rpc-infura@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/eth-json-rpc-infura/-/eth-json-rpc-infura-3.2.1.tgz#26702a821067862b72d979c016fd611502c6057f" - integrity sha512-W7zR4DZvyTn23Bxc0EWsq4XGDdD63+XPUCEhV2zQvQGavDVC4ZpFDK4k99qN7bd7/fjj37+rxmuBOBeIqCA5Mw== - dependencies: - cross-fetch "^2.1.1" - eth-json-rpc-middleware "^1.5.0" - json-rpc-engine "^3.4.0" - json-rpc-error "^2.0.0" - -eth-json-rpc-middleware@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz#5c9d4c28f745ccb01630f0300ba945f4bef9593f" - integrity sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q== - dependencies: - async "^2.5.0" - eth-query "^2.1.2" - eth-tx-summary "^3.1.2" - ethereumjs-block "^1.6.0" - ethereumjs-tx "^1.3.3" - ethereumjs-util "^5.1.2" - ethereumjs-vm "^2.1.0" - fetch-ponyfill "^4.0.0" - json-rpc-engine "^3.6.0" - json-rpc-error "^2.0.0" - json-stable-stringify "^1.0.1" - promise-to-callback "^1.0.0" - tape "^4.6.3" - -eth-lib@0.2.8: - version "0.2.8" - resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.8.tgz#b194058bef4b220ad12ea497431d6cb6aa0623c8" - integrity sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw== - dependencies: - bn.js "^4.11.6" - elliptic "^6.4.0" - xhr-request-promise "^0.1.2" - -eth-lib@^0.1.26: - version "0.1.29" - resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.1.29.tgz#0c11f5060d42da9f931eab6199084734f4dbd1d9" - integrity sha512-bfttrr3/7gG4E02HoWTDUcDDslN003OlOoBxk9virpAZQ1ja/jDgwkWB8QfJF7ojuEowrqy+lzp9VcJG7/k5bQ== - dependencies: - bn.js "^4.11.6" - elliptic "^6.4.0" - nano-json-stream-parser "^0.1.2" - servify "^0.1.12" - ws "^3.0.0" - xhr-request-promise "^0.1.2" - -eth-query@^2.0.2, eth-query@^2.1.0, eth-query@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/eth-query/-/eth-query-2.1.2.tgz#d6741d9000106b51510c72db92d6365456a6da5e" - integrity sha512-srES0ZcvwkR/wd5OQBRA1bIJMww1skfGS0s8wlwK3/oNP4+wnds60krvu5R1QbpRQjMmpG5OMIWro5s7gvDPsA== - dependencies: - json-rpc-random-id "^1.0.0" - xtend "^4.0.1" - -eth-sig-util@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-3.0.0.tgz#75133b3d7c20a5731af0690c385e184ab942b97e" - integrity sha512-4eFkMOhpGbTxBQ3AMzVf0haUX2uTur7DpWiHzWyTURa28BVJJtOkcb9Ok5TV0YvEPG61DODPW7ZUATbJTslioQ== - dependencies: - buffer "^5.2.1" - elliptic "^6.4.0" - ethereumjs-abi "0.6.5" - ethereumjs-util "^5.1.1" - tweetnacl "^1.0.0" - tweetnacl-util "^0.15.0" - -eth-sig-util@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-1.4.2.tgz#8d958202c7edbaae839707fba6f09ff327606210" - integrity sha512-iNZ576iTOGcfllftB73cPB5AN+XUQAT/T8xzsILsghXC1o8gJUqe3RHlcDqagu+biFpYQ61KQrZZJza8eRSYqw== - dependencies: - ethereumjs-abi "git+https://github.com/ethereumjs/ethereumjs-abi.git" - ethereumjs-util "^5.1.1" - -eth-tx-summary@^3.1.2: - version "3.2.4" - resolved "https://registry.yarnpkg.com/eth-tx-summary/-/eth-tx-summary-3.2.4.tgz#e10eb95eb57cdfe549bf29f97f1e4f1db679035c" - integrity sha512-NtlDnaVZah146Rm8HMRUNMgIwG/ED4jiqk0TME9zFheMl1jOp6jL1m0NKGjJwehXQ6ZKCPr16MTr+qspKpEXNg== - dependencies: - async "^2.1.2" - clone "^2.0.0" - concat-stream "^1.5.1" - end-of-stream "^1.1.0" - eth-query "^2.0.2" - ethereumjs-block "^1.4.1" - ethereumjs-tx "^1.1.1" - ethereumjs-util "^5.0.1" - ethereumjs-vm "^2.6.0" - through2 "^2.0.3" - -ethashjs@~0.0.7: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ethashjs/-/ethashjs-0.0.8.tgz#227442f1bdee409a548fb04136e24c874f3aa6f9" - integrity sha512-/MSbf/r2/Ld8o0l15AymjOTlPqpN8Cr4ByUEA9GtR4x0yAh3TdtDzEg29zMjXCNPI7u6E5fOQdj/Cf9Tc7oVNw== - dependencies: - async "^2.1.2" - buffer-xor "^2.0.1" - ethereumjs-util "^7.0.2" - miller-rabin "^4.0.0" - -ethereum-bloom-filters@^1.0.6: - version "1.0.10" - resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz#3ca07f4aed698e75bd134584850260246a5fed8a" - integrity sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA== - dependencies: - js-sha3 "^0.8.0" - -ethereum-common@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.2.0.tgz#13bf966131cce1eeade62a1b434249bb4cb120ca" - integrity sha512-XOnAR/3rntJgbCdGhqdaLIxDLWKLmsZOGhHdBKadEr6gEnJLH52k93Ou+TUdFaPN3hJc3isBZBal3U/XZ15abA== - -ethereum-common@^0.0.18: - version "0.0.18" - resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.0.18.tgz#2fdc3576f232903358976eb39da783213ff9523f" - integrity sha512-EoltVQTRNg2Uy4o84qpa2aXymXDJhxm7eos/ACOg0DG4baAbMjhbdAEsx9GeE8sC3XCxnYvrrzZDH8D8MtA2iQ== - ethereum-cryptography@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz#8d6143cfc3d74bf79bbd8edecdf29e4ae20dd191" @@ -3876,26 +2340,7 @@ ethereum-cryptography@^1.0.3: "@scure/bip32" "1.1.0" "@scure/bip39" "1.1.0" -ethereum-waffle@^3.4.0: - version "3.4.4" - resolved "https://registry.yarnpkg.com/ethereum-waffle/-/ethereum-waffle-3.4.4.tgz#1378b72040697857b7f5e8f473ca8f97a37b5840" - integrity sha512-PA9+jCjw4WC3Oc5ocSMBj5sXvueWQeAbvCA+hUlb6oFgwwKyq5ka3bWQ7QZcjzIX+TdFkxP4IbFmoY2D8Dkj9Q== - dependencies: - "@ethereum-waffle/chai" "^3.4.4" - "@ethereum-waffle/compiler" "^3.4.4" - "@ethereum-waffle/mock-contract" "^3.4.4" - "@ethereum-waffle/provider" "^3.4.4" - ethers "^5.0.1" - -ethereumjs-abi@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz#5a637ef16ab43473fa72a29ad90871405b3f5241" - integrity sha512-rCjJZ/AE96c/AAZc6O3kaog4FhOsAViaysBxqJNy2+LHP0ttH0zkZ7nXdVHOAyt6lFwLO0nlCwWszysG/ao1+g== - dependencies: - bn.js "^4.10.0" - ethereumjs-util "^4.3.0" - -ethereumjs-abi@0.6.8, ethereumjs-abi@^0.6.8: +ethereumjs-abi@^0.6.8: version "0.6.8" resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz#71bc152db099f70e62f108b7cdfca1b362c6fcae" integrity sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA== @@ -3903,96 +2348,7 @@ ethereumjs-abi@0.6.8, ethereumjs-abi@^0.6.8: bn.js "^4.11.8" ethereumjs-util "^6.0.0" -"ethereumjs-abi@git+https://github.com/ethereumjs/ethereumjs-abi.git": - version "0.6.8" - resolved "git+https://github.com/ethereumjs/ethereumjs-abi.git#ee3994657fa7a427238e6ba92a84d0b529bbcde0" - dependencies: - bn.js "^4.11.8" - ethereumjs-util "^6.0.0" - -ethereumjs-account@3.0.0, ethereumjs-account@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ethereumjs-account/-/ethereumjs-account-3.0.0.tgz#728f060c8e0c6e87f1e987f751d3da25422570a9" - integrity sha512-WP6BdscjiiPkQfF9PVfMcwx/rDvfZTjFKY0Uwc09zSQr9JfIVH87dYIJu0gNhBhpmovV4yq295fdllS925fnBA== - dependencies: - ethereumjs-util "^6.0.0" - rlp "^2.2.1" - safe-buffer "^5.1.1" - -ethereumjs-account@^2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/ethereumjs-account/-/ethereumjs-account-2.0.5.tgz#eeafc62de544cb07b0ee44b10f572c9c49e00a84" - integrity sha512-bgDojnXGjhMwo6eXQC0bY6UK2liSFUSMwwylOmQvZbSl/D7NXQ3+vrGO46ZeOgjGfxXmgIeVNDIiHw7fNZM4VA== - dependencies: - ethereumjs-util "^5.0.0" - rlp "^2.0.0" - safe-buffer "^5.1.1" - -ethereumjs-block@2.2.2, ethereumjs-block@^2.2.2, ethereumjs-block@~2.2.0, ethereumjs-block@~2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-2.2.2.tgz#c7654be7e22df489fda206139ecd63e2e9c04965" - integrity sha512-2p49ifhek3h2zeg/+da6XpdFR3GlqY3BIEiqxGF8j9aSRIgkb7M1Ky+yULBKJOu8PAZxfhsYA+HxUk2aCQp3vg== - dependencies: - async "^2.0.1" - ethereumjs-common "^1.5.0" - ethereumjs-tx "^2.1.1" - ethereumjs-util "^5.0.0" - merkle-patricia-tree "^2.1.2" - -ethereumjs-block@^1.2.2, ethereumjs-block@^1.4.1, ethereumjs-block@^1.6.0: - version "1.7.1" - resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-1.7.1.tgz#78b88e6cc56de29a6b4884ee75379b6860333c3f" - integrity sha512-B+sSdtqm78fmKkBq78/QLKJbu/4Ts4P2KFISdgcuZUPDm9x+N7qgBPIIFUGbaakQh8bzuquiRVbdmvPKqbILRg== - dependencies: - async "^2.0.1" - ethereum-common "0.2.0" - ethereumjs-tx "^1.2.2" - ethereumjs-util "^5.0.0" - merkle-patricia-tree "^2.1.2" - -ethereumjs-blockchain@^4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/ethereumjs-blockchain/-/ethereumjs-blockchain-4.0.4.tgz#30f2228dc35f6dcf94423692a6902604ae34960f" - integrity sha512-zCxaRMUOzzjvX78DTGiKjA+4h2/sF0OYL1QuPux0DHpyq8XiNoF5GYHtb++GUxVlMsMfZV7AVyzbtgcRdIcEPQ== - dependencies: - async "^2.6.1" - ethashjs "~0.0.7" - ethereumjs-block "~2.2.2" - ethereumjs-common "^1.5.0" - ethereumjs-util "^6.1.0" - flow-stoplight "^1.0.0" - level-mem "^3.0.1" - lru-cache "^5.1.1" - rlp "^2.2.2" - semaphore "^1.1.0" - -ethereumjs-common@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-1.5.0.tgz#d3e82fc7c47c0cef95047f431a99485abc9bb1cd" - integrity sha512-SZOjgK1356hIY7MRj3/ma5qtfr/4B5BL+G4rP/XSMYr2z1H5el4RX5GReYCKmQmYI/nSBmRnwrZ17IfHuG0viQ== - -ethereumjs-common@^1.1.0, ethereumjs-common@^1.3.2, ethereumjs-common@^1.5.0: - version "1.5.2" - resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-1.5.2.tgz#2065dbe9214e850f2e955a80e650cb6999066979" - integrity sha512-hTfZjwGX52GS2jcVO6E2sx4YuFnf0Fhp5ylo4pEPhEffNln7vS59Hr5sLnp3/QCazFLluuBZ+FZ6J5HTp0EqCA== - -ethereumjs-tx@2.1.2, ethereumjs-tx@^2.1.1, ethereumjs-tx@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-2.1.2.tgz#5dfe7688bf177b45c9a23f86cf9104d47ea35fed" - integrity sha512-zZEK1onCeiORb0wyCXUvg94Ve5It/K6GD1K+26KfFKodiBiS6d9lfCXlUKGBBdQ+bv7Day+JK0tj1K+BeNFRAw== - dependencies: - ethereumjs-common "^1.5.0" - ethereumjs-util "^6.0.0" - -ethereumjs-tx@^1.1.1, ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.3: - version "1.3.7" - resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz#88323a2d875b10549b8347e09f4862b546f3d89a" - integrity sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA== - dependencies: - ethereum-common "^0.0.18" - ethereumjs-util "^5.0.0" - -ethereumjs-util@6.2.1, ethereumjs-util@^6.0.0, ethereumjs-util@^6.1.0, ethereumjs-util@^6.2.0, ethereumjs-util@^6.2.1: +ethereumjs-util@^6.0.0, ethereumjs-util@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz#fcb4e4dd5ceacb9d2305426ab1a5cd93e3163b69" integrity sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw== @@ -4005,31 +2361,7 @@ ethereumjs-util@6.2.1, ethereumjs-util@^6.0.0, ethereumjs-util@^6.1.0, ethereumj ethjs-util "0.1.6" rlp "^2.2.3" -ethereumjs-util@^4.3.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-4.5.1.tgz#f4bf9b3b515a484e3cc8781d61d9d980f7c83bd0" - integrity sha512-WrckOZ7uBnei4+AKimpuF1B3Fv25OmoRgmYCpGsP7u8PFxXAmAgiJSYT2kRWnt6fVIlKaQlZvuwXp7PIrmn3/w== - dependencies: - bn.js "^4.8.0" - create-hash "^1.1.2" - elliptic "^6.5.2" - ethereum-cryptography "^0.1.3" - rlp "^2.0.0" - -ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereumjs-util@^5.1.2, ethereumjs-util@^5.1.3, ethereumjs-util@^5.1.5, ethereumjs-util@^5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz#a833f0e5fca7e5b361384dc76301a721f537bf65" - integrity sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ== - dependencies: - bn.js "^4.11.0" - create-hash "^1.1.2" - elliptic "^6.5.2" - ethereum-cryptography "^0.1.3" - ethjs-util "^0.1.3" - rlp "^2.0.0" - safe-buffer "^5.1.1" - -ethereumjs-util@^7.0.2, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.4, ethereumjs-util@^7.1.5: +ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.4, ethereumjs-util@^7.1.5: version "7.1.5" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg== @@ -4040,59 +2372,6 @@ ethereumjs-util@^7.0.2, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.1, ethereum ethereum-cryptography "^0.1.3" rlp "^2.2.4" -ethereumjs-vm@4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-4.2.0.tgz#e885e861424e373dbc556278f7259ff3fca5edab" - integrity sha512-X6qqZbsY33p5FTuZqCnQ4+lo957iUJMM6Mpa6bL4UW0dxM6WmDSHuI4j/zOp1E2TDKImBGCJA9QPfc08PaNubA== - dependencies: - async "^2.1.2" - async-eventemitter "^0.2.2" - core-js-pure "^3.0.1" - ethereumjs-account "^3.0.0" - ethereumjs-block "^2.2.2" - ethereumjs-blockchain "^4.0.3" - ethereumjs-common "^1.5.0" - ethereumjs-tx "^2.1.2" - ethereumjs-util "^6.2.0" - fake-merkle-patricia-tree "^1.0.1" - functional-red-black-tree "^1.0.1" - merkle-patricia-tree "^2.3.2" - rustbn.js "~0.2.0" - safe-buffer "^5.1.1" - util.promisify "^1.0.0" - -ethereumjs-vm@^2.1.0, ethereumjs-vm@^2.3.4, ethereumjs-vm@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz#76243ed8de031b408793ac33907fb3407fe400c6" - integrity sha512-r/XIUik/ynGbxS3y+mvGnbOKnuLo40V5Mj1J25+HEO63aWYREIqvWeRO/hnROlMBE5WoniQmPmhiaN0ctiHaXw== - dependencies: - async "^2.1.2" - async-eventemitter "^0.2.2" - ethereumjs-account "^2.0.3" - ethereumjs-block "~2.2.0" - ethereumjs-common "^1.1.0" - ethereumjs-util "^6.0.0" - fake-merkle-patricia-tree "^1.0.1" - functional-red-black-tree "^1.0.1" - merkle-patricia-tree "^2.3.2" - rustbn.js "~0.2.0" - safe-buffer "^5.1.1" - -ethereumjs-wallet@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-0.6.5.tgz#685e9091645cee230ad125c007658833991ed474" - integrity sha512-MDwjwB9VQVnpp/Dc1XzA6J1a3wgHQ4hSvA1uWNatdpOrtCbPVuQSKSyRnjLvS0a+KKMw2pvQ9Ybqpb3+eW8oNA== - dependencies: - aes-js "^3.1.1" - bs58check "^2.1.2" - ethereum-cryptography "^0.1.3" - ethereumjs-util "^6.0.0" - randombytes "^2.0.6" - safe-buffer "^5.1.2" - scryptsy "^1.2.1" - utf8 "^3.0.0" - uuid "^3.3.2" - ethers@^4.0.40: version "4.0.49" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.49.tgz#0eb0e9161a0c8b4761be547396bbe2fb121a8894" @@ -4108,7 +2387,7 @@ ethers@^4.0.40: uuid "2.0.1" xmlhttprequest "1.8.0" -ethers@^5.0.1, ethers@^5.0.2, ethers@^5.5.2: +ethers@^5.5.2: version "5.6.9" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.6.9.tgz#4e12f8dfcb67b88ae7a78a9519b384c23c576a4d" integrity sha512-lMGC2zv9HC5EC+8r429WaWu3uWJUCgUCt8xxKCFqkrFuBDZXDYIdzDUECxzjf2BMF8IVBByY1EBoGSL3RTm8RA== @@ -4144,15 +2423,7 @@ ethers@^5.0.1, ethers@^5.0.2, ethers@^5.5.2: "@ethersproject/web" "5.6.1" "@ethersproject/wordlists" "5.6.1" -ethjs-unit@0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" - integrity sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw== - dependencies: - bn.js "4.11.6" - number-to-bn "1.7.0" - -ethjs-util@0.1.6, ethjs-util@^0.1.3, ethjs-util@^0.1.6: +ethjs-util@0.1.6, ethjs-util@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w== @@ -4165,17 +2436,7 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter3@4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" - integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== - -events@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: +evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== @@ -4198,78 +2459,6 @@ execa@^6.1.0: signal-exit "^3.0.7" strip-final-newline "^3.0.0" -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA== - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -express@^4.14.0: - version "4.18.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" - integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.0" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.10.3" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -ext@^1.1.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.6.0.tgz#3871d50641e874cc172e2b53f919842d19db4c52" - integrity sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg== - dependencies: - type "^2.5.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -4284,20 +2473,6 @@ external-editor@^3.0.3: iconv-lite "^0.4.24" tmp "^0.0.33" -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -4308,13 +2483,6 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== -fake-merkle-patricia-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fake-merkle-patricia-tree/-/fake-merkle-patricia-tree-1.0.1.tgz#4b8c3acfb520afadf9860b1f14cd8ce3402cddd3" - integrity sha512-Tgq37lkc9pUIgIKw5uitNUKcgcYL3R6JvXtKQbOf/ZSavXbidsksgp/pAY6p//uhw0I4yoMsvTSovvVIsk/qxA== - dependencies: - checkpoint-store "^1.1.0" - fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -4353,13 +2521,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -fetch-ponyfill@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/fetch-ponyfill/-/fetch-ponyfill-4.1.0.tgz#ae3ce5f732c645eab87e4ae8793414709b239893" - integrity sha512-knK9sGskIg2T7OnYLdZ2hZXn0CtDrAIBxYQLpmEf0BqfdWnwmM1weccUl5+4EdA44tzNSFAuxITPbXtPehUB3g== - dependencies: - node-fetch "~1.7.1" - figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -4374,16 +2535,6 @@ file-entry-cache@^5.0.1: dependencies: flat-cache "^2.0.1" -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ== - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -4391,27 +2542,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -find-replace@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-1.0.3.tgz#b88e7364d2d9c959559f388c66670d6130441fa0" - integrity sha512-KrUnjzDCD9426YnCP56zGYy/eieTnhtK6Vn++j+JJzmlsWWwEkDnsyVF575spT6HJ6Ow9tlbT3TQTDsa+O4UWA== - dependencies: - array-back "^1.0.4" - test-value "^2.1.0" - find-replace@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" @@ -4434,14 +2564,6 @@ find-up@5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -find-up@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" - integrity sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA== - dependencies: - path-exists "^2.0.0" - pinkie-promise "^2.0.0" - find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" @@ -4449,21 +2571,6 @@ find-up@^2.1.0: dependencies: locate-path "^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" - integrity sha512-dVtfb0WuQG+8Ag2uWkbG79hOUzEsRrhBzgfn86g2sJPkzmcpGdghbNTfUKGTxymFrY/tLIodDzLoW9nOJ4FY8Q== - dependencies: - fs-extra "^4.0.3" - micromatch "^3.1.4" - -find-yarn-workspace-root@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" - integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== - dependencies: - micromatch "^4.0.2" - flat-cache@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" @@ -4490,11 +2597,6 @@ flatted@^2.0.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== -flow-stoplight@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/flow-stoplight/-/flow-stoplight-1.0.0.tgz#4a292c5bcff8b39fa6cc0cb1a853d86f27eeff7b" - integrity sha512-rDjbZUKpN8OYhB0IE/vY/I8UWO/602IIJEU/76Tv4LvYnwHCk0BCsvz4eRr9n+FQcri7L5cyaXOo0+/Kh4HisA== - fmix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/fmix/-/fmix-0.1.0.tgz#c7bbf124dec42c9d191cfb947d0a9778dd986c0c" @@ -4507,18 +2609,6 @@ follow-redirects@^1.12.1, follow-redirects@^1.14.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== -for-each@^0.3.3, for-each@~0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== - forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -4533,15 +2623,6 @@ form-data@^2.2.0: combined-stream "^1.0.6" mime-types "^2.1.12" -form-data@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - form-data@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" @@ -4560,11 +2641,6 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - fp-ts@1.19.3: version "1.19.3" resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.19.3.tgz#261a60d1088fbff01f91256f91d21d0caaaaa96f" @@ -4575,18 +2651,6 @@ fp-ts@^1.0.0: resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.19.5.tgz#3da865e585dfa1fdfd51785417357ac50afc520a" integrity sha512-wDNqTimnzs8QqpldiId9OavWK2NptormjXnRJTQecNjzwfyp6P/8s/zG8e4h3ja3oqkKaY72UlTjQYt/1yXf9A== -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA== - dependencies: - map-cache "^0.2.2" - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - fs-extra@^0.30.0: version "0.30.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" @@ -4607,15 +2671,6 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^4.0.2, fs-extra@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" - integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-extra@^7.0.0, fs-extra@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -4644,13 +2699,6 @@ fs-extra@^9.1.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-minipass@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -4696,48 +2744,6 @@ functions-have-names@^1.2.2: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== -ganache-core@^2.13.2: - version "2.13.2" - resolved "https://registry.yarnpkg.com/ganache-core/-/ganache-core-2.13.2.tgz#27e6fc5417c10e6e76e2e646671869d7665814a3" - integrity sha512-tIF5cR+ANQz0+3pHWxHjIwHqFXcVo0Mb+kcsNhglNFALcYo49aQpnS9dqHartqPfMFjiHh/qFoD3mYK0d/qGgw== - dependencies: - abstract-leveldown "3.0.0" - async "2.6.2" - bip39 "2.5.0" - cachedown "1.0.0" - clone "2.1.2" - debug "3.2.6" - encoding-down "5.0.4" - eth-sig-util "3.0.0" - ethereumjs-abi "0.6.8" - ethereumjs-account "3.0.0" - ethereumjs-block "2.2.2" - ethereumjs-common "1.5.0" - ethereumjs-tx "2.1.2" - ethereumjs-util "6.2.1" - ethereumjs-vm "4.2.0" - heap "0.2.6" - keccak "3.0.1" - level-sublevel "6.6.4" - levelup "3.1.1" - lodash "4.17.20" - lru-cache "5.1.1" - merkle-patricia-tree "3.0.0" - patch-package "6.2.2" - seedrandom "3.0.1" - source-map-support "0.5.12" - tmp "0.1.0" - web3-provider-engine "14.2.1" - websocket "1.0.32" - optionalDependencies: - ethereumjs-wallet "0.6.5" - web3 "1.2.11" - -get-caller-file@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== - get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -4762,25 +2768,6 @@ get-port@^3.1.0: resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" integrity sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg== -get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ== - -get-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -4794,11 +2781,6 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== - getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -4837,7 +2819,7 @@ glob@7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.2, glob@^7.1.3, glob@^7.1.6, glob@~7.2.3: +glob@^7.1.2, glob@^7.1.3, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -4849,24 +2831,11 @@ glob@^7.1.2, glob@^7.1.3, glob@^7.1.6, glob@~7.2.3: once "^1.3.0" path-is-absolute "^1.0.0" -global@~4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" - integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== - dependencies: - min-document "^2.19.0" - process "^0.11.10" - globals@^11.7.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^9.18.0: - version "9.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" - integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== - globby@^11.0.0, globby@^11.0.1: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -4879,44 +2848,7 @@ globby@^11.0.0, globby@^11.0.1: merge2 "^1.4.1" slash "^3.0.0" -got@9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - -got@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" - integrity sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw== - dependencies: - decompress-response "^3.2.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - is-plain-obj "^1.1.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - isurl "^1.0.0-alpha5" - lowercase-keys "^1.0.0" - p-cancelable "^0.3.0" - p-timeout "^1.1.1" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - url-parse-lax "^1.0.0" - url-to-options "^1.0.1" - -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== @@ -5042,13 +2974,6 @@ hardhat@^2.7.1: uuid "^8.3.2" ws "^7.4.6" -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== - dependencies: - ansi-regex "^2.0.0" - has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -5071,23 +2996,11 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.1.1" -has-symbol-support-x@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" - integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== - -has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: +has-symbols@^1.0.0, has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-to-string-tag-x@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" - integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw== - dependencies: - has-symbol-support-x "^1.4.1" - has-tostringtag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" @@ -5095,38 +3008,7 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q== - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw== - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ== - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ== - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -has@^1.0.3, has@~1.0.3: +has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== @@ -5163,11 +3045,6 @@ he@1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -heap@0.2.6: - version "0.2.6" - resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.6.tgz#087e1f10b046932fc8594dd9e6d378afc9d1e5ac" - integrity sha512-MzzWcnfB1e4EG2vHi3dXHoBupmuXNZzx6pY6HldVS55JKKBoq3xOyzfSaZRkJp37HIhEYC78knabHff3zc4dQQ== - hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -5177,19 +3054,6 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -home-or-tmp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" - integrity sha512-ycURW7oUxE2sNiPVw1HVEFsW+ecOpJ5zaj7eC0RlwhibhRBod20muUN8qu/gzx956YrLolVvs1MTXwKgC2rVEg== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.1" - -hosted-git-info@^2.1.4, hosted-git-info@^2.6.0: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - http-basic@^8.1.1: version "8.1.3" resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-8.1.3.tgz#a7cabee7526869b9b710136970805b1004261bbf" @@ -5200,11 +3064,6 @@ http-basic@^8.1.1: http-response-object "^3.0.1" parse-cache-control "^1.0.1" -http-cache-semantics@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" - integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== - http-errors@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" @@ -5216,11 +3075,6 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" -http-https@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/http-https/-/http-https-1.0.0.tgz#2f908dd5f1db4068c058cd6e6d4ce392c913389b" - integrity sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg== - http-response-object@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-3.0.2.tgz#7f435bb210454e4360d074ef1f989d5ea8aa9810" @@ -5262,20 +3116,6 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -idna-uts46-hx@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz#a1dc5c4df37eee522bf66d969cc980e00e8711f9" - integrity sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA== - dependencies: - punycode "2.1.0" - ieee754@^1.1.13: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -5345,7 +3185,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -5378,18 +3218,6 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" -invariant@^2.2.2: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - -invert-kv@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - integrity sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ== - io-ts@1.10.4: version "1.10.4" resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-1.10.4.tgz#cd5401b138de88e4f920adbcb7026e2d1967e6e2" @@ -5397,33 +3225,6 @@ io-ts@1.10.4: dependencies: fp-ts "^1.0.0" -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A== - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - -is-arguments@^1.0.4: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" - integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -5451,49 +3252,16 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - is-buffer@~2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.4: +is-callable@^1.1.4, is-callable@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - -is-core-module@^2.9.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" - integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== - dependencies: - has "^1.0.3" - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg== - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - is-date-object@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" @@ -5501,24 +3269,6 @@ is-date-object@^1.0.1: dependencies: has-tostringtag "^1.0.0" -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - is-directory@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" @@ -5529,40 +3279,11 @@ is-docker@^2.0.0: resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -is-finite@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" - integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== - -is-fn@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fn/-/is-fn-1.0.0.tgz#9543d5de7bcf5b08a22ec8a20bae6e286d510d8c" - integrity sha512-XoFPJQmsAShb3jEQRfzf2rqXavq7fIqF/jOekp308JlThqrODnMpweVSGilKTCXELfLhltGP2AGgbQGVP8F1dg== - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw== - dependencies: - number-is-nan "^1.0.0" - is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -5578,11 +3299,6 @@ is-fullwidth-code-point@^4.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== -is-function@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" - integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ== - is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -5607,41 +3323,17 @@ is-number-object@^1.0.4: dependencies: has-tostringtag "^1.0.0" -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg== - dependencies: - kind-of "^3.0.2" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" - integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA== - -is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== - is-plain-obj@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regex@^1.0.4, is-regex@^1.1.4, is-regex@~1.1.4: +is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== @@ -5649,11 +3341,6 @@ is-regex@^1.0.4, is-regex@^1.1.4, is-regex@~1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-retry-allowed@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" - integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== - is-shared-array-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" @@ -5661,11 +3348,6 @@ is-shared-array-buffer@^1.0.2: dependencies: call-bind "^1.0.2" -is-stream@^1.0.0, is-stream@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== - is-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" @@ -5685,7 +3367,7 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typedarray@^1.0.0, is-typedarray@~1.0.0: +is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== @@ -5695,16 +3377,6 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -is-url@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" - integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== - -is-utf8@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - integrity sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q== - is-weakref@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" @@ -5712,11 +3384,6 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - is-wsl@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" @@ -5724,12 +3391,7 @@ is-wsl@^2.1.1: dependencies: is-docker "^2.0.0" -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== - -isarray@1.0.0, isarray@~1.0.0: +isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== @@ -5739,32 +3401,12 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA== - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== -isurl@^1.0.0-alpha5: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" - integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w== - dependencies: - has-to-string-tag-x "^1.2.0" - is-object "^1.0.1" - -js-sha3@0.5.7, js-sha3@^0.5.7: +js-sha3@0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" integrity sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g== @@ -5774,16 +3416,11 @@ js-sha3@0.8.0, js-sha3@^0.8.0: resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: +js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - integrity sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg== - js-yaml@3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" @@ -5812,50 +3449,11 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== -jsesc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" - integrity sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA== - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== - -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ== - json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== -json-rpc-engine@^3.4.0, json-rpc-engine@^3.6.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-3.8.0.tgz#9d4ff447241792e1d0a232f6ef927302bb0c62a9" - integrity sha512-6QNcvm2gFuuK4TKU1uwfH0Qd/cOSb9c1lls0gbnIhciktIUQJwz6NQNAW4B1KiGPenv7IKu97V222Yo1bNhGuA== - dependencies: - async "^2.0.1" - babel-preset-env "^1.7.0" - babelify "^7.3.0" - json-rpc-error "^2.0.0" - promise-to-callback "^1.0.0" - safe-event-emitter "^1.0.1" - -json-rpc-error@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/json-rpc-error/-/json-rpc-error-2.0.0.tgz#a7af9c202838b5e905c7250e547f1aff77258a02" - integrity sha512-EwUeWP+KgAZ/xqFpaP6YDAXMtCJi+o/QQpCQFIYyxr01AdADi2y413eM8hSqJcoQym9WMePAJWoaODEJufC4Ug== - dependencies: - inherits "^2.0.1" - -json-rpc-random-id@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-rpc-random-id/-/json-rpc-random-id-1.0.1.tgz#ba49d96aded1444dbb8da3d203748acbbcdec8c8" - integrity sha512-RJ9YYNCkhVDBuP4zN5BBtYAzEl03yq/jIIsyif0JY9qyJuQQZNeDK7anAPKKlyEtLSj2s8h6hNh2F8zO5q7ScA== - json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -5871,23 +3469,11 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json-stable-stringify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - integrity sha512-i/J297TW6xyj7sDFa7AmBPkQvLIxWr2kKPWI26tXydnZrzVAocNqn5DMNT1Mzk0vit1V5UkRM7C1KdVNp7Lmcg== - dependencies: - jsonify "~0.0.0" - json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== -json5@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" - integrity sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw== - json5@^2.1.3: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" @@ -5916,11 +3502,6 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - integrity sha512-trvBk1ki43VZptdBI5rIlG4YOzyeH/WefQt5rj1grasPn4iiZWKet8nkgc4GlsAylaztn0qZfUYOiTsASJFdNA== - jsprim@^1.2.2: version "1.4.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" @@ -5931,14 +3512,6 @@ jsprim@^1.2.2: json-schema "0.4.0" verror "1.10.0" -keccak@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.1.tgz#ae30a0e94dbe43414f741375cff6d64c8bea0bff" - integrity sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA== - dependencies: - node-addon-api "^2.0.0" - node-gyp-build "^4.2.0" - keccak@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.2.tgz#4c2c6e8c54e04f2670ee49fa734eb9da152206e0" @@ -5948,44 +3521,6 @@ keccak@^3.0.0: node-gyp-build "^4.2.0" readable-stream "^3.6.0" -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw== - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -klaw-sync@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" - integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== - dependencies: - graceful-fs "^4.1.11" - klaw@^1.0.0: version "1.3.1" resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" @@ -5993,13 +3528,6 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - integrity sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw== - dependencies: - invert-kv "^1.0.0" - level-codec@^9.0.0: version "9.0.2" resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-9.0.2.tgz#fd60df8c64786a80d44e63423096ffead63d8cbc" @@ -6007,23 +3535,11 @@ level-codec@^9.0.0: dependencies: buffer "^5.6.0" -level-codec@~7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-7.0.1.tgz#341f22f907ce0f16763f24bddd681e395a0fb8a7" - integrity sha512-Ua/R9B9r3RasXdRmOtd+t9TCOEIIlts+TN/7XTT2unhDaL6sJn83S3rUyljbr6lVtw49N3/yA0HHjpV6Kzb2aQ== - level-concat-iterator@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz#1d1009cf108340252cb38c51f9727311193e6263" integrity sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw== -level-errors@^1.0.3: - version "1.1.2" - resolved "https://registry.yarnpkg.com/level-errors/-/level-errors-1.1.2.tgz#4399c2f3d3ab87d0625f7e3676e2d807deff404d" - integrity sha512-Sw/IJwWbPKF5Ai4Wz60B52yj0zYeqzObLh8k1Tk88jVmD51cJSKWSYpRyhVIvFzZdvsPqlH5wfhp/yxdsaQH4w== - dependencies: - errno "~0.1.1" - level-errors@^2.0.0, level-errors@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/level-errors/-/level-errors-2.0.1.tgz#2132a677bf4e679ce029f517c2f17432800c05c8" @@ -6031,41 +3547,6 @@ level-errors@^2.0.0, level-errors@~2.0.0: dependencies: errno "~0.1.1" -level-errors@~1.0.3: - version "1.0.5" - resolved "https://registry.yarnpkg.com/level-errors/-/level-errors-1.0.5.tgz#83dbfb12f0b8a2516bdc9a31c4876038e227b859" - integrity sha512-/cLUpQduF6bNrWuAC4pwtUKA5t669pCsCi2XbmojG2tFeOr9j6ShtdDCtFFQO1DRt+EVZhx9gPzP9G2bUaG4ig== - dependencies: - errno "~0.1.1" - -level-iterator-stream@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/level-iterator-stream/-/level-iterator-stream-2.0.3.tgz#ccfff7c046dcf47955ae9a86f46dfa06a31688b4" - integrity sha512-I6Heg70nfF+e5Y3/qfthJFexhRw/Gi3bIymCoXAlijZdAcLaPuWSJs3KXyTYf23ID6g0o2QF62Yh+grOXY3Rig== - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.5" - xtend "^4.0.0" - -level-iterator-stream@~1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/level-iterator-stream/-/level-iterator-stream-1.3.1.tgz#e43b78b1a8143e6fa97a4f485eb8ea530352f2ed" - integrity sha512-1qua0RHNtr4nrZBgYlpV0qHHeHpcRRWTxEZJ8xsemoHAXNL5tbooh4tPEEqIqsbWCAJBmUmkwYK/sW5OrFjWWw== - dependencies: - inherits "^2.0.1" - level-errors "^1.0.3" - readable-stream "^1.0.33" - xtend "^4.0.0" - -level-iterator-stream@~3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/level-iterator-stream/-/level-iterator-stream-3.0.1.tgz#2c98a4f8820d87cdacab3132506815419077c730" - integrity sha512-nEIQvxEED9yRThxvOrq8Aqziy4EGzrxSZK+QzEFAVuJvQ8glfyZ96GB6BoI4sBbLfjMXm2w4vu3Tkcm9obcY0g== - dependencies: - inherits "^2.0.1" - readable-stream "^2.3.6" - xtend "^4.0.0" - level-iterator-stream@~4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz#7ceba69b713b0d7e22fcc0d1f128ccdc8a24f79c" @@ -6075,14 +3556,6 @@ level-iterator-stream@~4.0.0: readable-stream "^3.4.0" xtend "^4.0.2" -level-mem@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/level-mem/-/level-mem-3.0.1.tgz#7ce8cf256eac40f716eb6489654726247f5a89e5" - integrity sha512-LbtfK9+3Ug1UmvvhR2DqLqXiPW1OJ5jEh0a3m9ZgAipiwpSxGj/qaVVy54RG5vAQN1nCuXqjvprCuKSCxcJHBg== - dependencies: - level-packager "~4.0.0" - memdown "~3.0.0" - level-mem@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/level-mem/-/level-mem-5.0.1.tgz#c345126b74f5b8aa376dc77d36813a177ef8251d" @@ -6099,37 +3572,6 @@ level-packager@^5.0.3: encoding-down "^6.3.0" levelup "^4.3.2" -level-packager@~4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/level-packager/-/level-packager-4.0.1.tgz#7e7d3016af005be0869bc5fa8de93d2a7f56ffe6" - integrity sha512-svCRKfYLn9/4CoFfi+d8krOtrp6RoX8+xm0Na5cgXMqSyRru0AnDYdLl+YI8u1FyS6gGZ94ILLZDE5dh2but3Q== - dependencies: - encoding-down "~5.0.0" - levelup "^3.0.0" - -level-post@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/level-post/-/level-post-1.0.7.tgz#19ccca9441a7cc527879a0635000f06d5e8f27d0" - integrity sha512-PWYqG4Q00asOrLhX7BejSajByB4EmG2GaKHfj3h5UmmZ2duciXLPGYWIjBzLECFWUGOZWlm5B20h/n3Gs3HKew== - dependencies: - ltgt "^2.1.2" - -level-sublevel@6.6.4: - version "6.6.4" - resolved "https://registry.yarnpkg.com/level-sublevel/-/level-sublevel-6.6.4.tgz#f7844ae893919cd9d69ae19d7159499afd5352ba" - integrity sha512-pcCrTUOiO48+Kp6F1+UAzF/OtWqLcQVTVF39HLdZ3RO8XBoXt+XVPKZO1vVr1aUoxHZA9OtD2e1v7G+3S5KFDA== - dependencies: - bytewise "~1.1.0" - level-codec "^9.0.0" - level-errors "^2.0.0" - level-iterator-stream "^2.0.3" - ltgt "~2.1.1" - pull-defer "^0.2.2" - pull-level "^2.0.3" - pull-stream "^3.6.8" - typewiselite "~1.0.0" - xtend "~4.0.0" - level-supports@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-1.0.1.tgz#2f530a596834c7301622521988e2c36bb77d122d" @@ -6137,23 +3579,6 @@ level-supports@~1.0.0: dependencies: xtend "^4.0.2" -level-ws@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/level-ws/-/level-ws-0.0.0.tgz#372e512177924a00424b0b43aef2bb42496d228b" - integrity sha512-XUTaO/+Db51Uiyp/t7fCMGVFOTdtLS/NIACxE/GHsij15mKzxksZifKVjlXDF41JMUP/oM1Oc4YNGdKnc3dVLw== - dependencies: - readable-stream "~1.0.15" - xtend "~2.1.1" - -level-ws@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/level-ws/-/level-ws-1.0.0.tgz#19a22d2d4ac57b18cc7c6ecc4bd23d899d8f603b" - integrity sha512-RXEfCmkd6WWFlArh3X8ONvQPm8jNpfA0s/36M4QzLqrLEIt1iJE9WBHLZ5vZJK6haMjJPJGJCQWfjMNnRcq/9Q== - dependencies: - inherits "^2.0.3" - readable-stream "^2.2.8" - xtend "^4.0.1" - level-ws@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/level-ws/-/level-ws-2.0.0.tgz#207a07bcd0164a0ec5d62c304b4615c54436d339" @@ -6163,29 +3588,6 @@ level-ws@^2.0.0: readable-stream "^3.1.0" xtend "^4.0.1" -levelup@3.1.1, levelup@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/levelup/-/levelup-3.1.1.tgz#c2c0b3be2b4dc316647c53b42e2f559e232d2189" - integrity sha512-9N10xRkUU4dShSRRFTBdNaBxofz+PGaIZO962ckboJZiNmLuhVT6FZ6ZKAsICKfUBO76ySaYU6fJWX/jnj3Lcg== - dependencies: - deferred-leveldown "~4.0.0" - level-errors "~2.0.0" - level-iterator-stream "~3.0.0" - xtend "~4.0.0" - -levelup@^1.2.1: - version "1.3.9" - resolved "https://registry.yarnpkg.com/levelup/-/levelup-1.3.9.tgz#2dbcae845b2bb2b6bea84df334c475533bbd82ab" - integrity sha512-VVGHfKIlmw8w1XqpGOAGwq6sZm2WwWLmlDcULkKWQXEA5EopA8OBNJ2Ck2v6bdk8HeEZSbCSEgzXadyQFm76sQ== - dependencies: - deferred-leveldown "~1.2.1" - level-codec "~7.0.0" - level-errors "~1.0.3" - level-iterator-stream "~1.3.0" - prr "~1.0.1" - semver "~5.4.1" - xtend "~4.0.0" - levelup@^4.3.2: version "4.4.0" resolved "https://registry.yarnpkg.com/levelup/-/levelup-4.4.0.tgz#f89da3a228c38deb49c48f88a70fb71f01cafed6" @@ -6243,17 +3645,6 @@ listr2@^4.0.5: through "^2.3.8" wrap-ansi "^7.0.0" -load-json-file@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" - integrity sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A== - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" - locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -6277,22 +3668,12 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.assign@^4.0.3, lodash.assign@^4.0.6: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" - integrity sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw== - lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== -lodash@4.17.20: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== - -lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: +lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -6322,23 +3703,6 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" -looper@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/looper/-/looper-2.0.0.tgz#66cd0c774af3d4fedac53794f742db56da8f09ec" - integrity sha512-6DzMHJcjbQX/UPHc1rRCBfKlLwDkvuGZ715cIR36wSdYqWXFT35uLXq5P/2orl3tz+t+VOVPxw4yPinQlUDGDQ== - -looper@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/looper/-/looper-3.0.0.tgz#2efa54c3b1cbaba9b94aee2e5914b0be57fbb749" - integrity sha512-LJ9wplN/uSn72oJRsXTx+snxPet5c8XiZmOKCm906NVYu+ag6SB6vUcnJcWxgnl2NfbIyeobAn7Bwv6xRj2XJg== - -loose-envify@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - loupe@^2.3.1: version "2.3.4" resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3" @@ -6346,30 +3710,13 @@ loupe@^2.3.1: dependencies: get-func-name "^2.0.0" -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - -lru-cache@5.1.1, lru-cache@^5.1.1: +lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: yallist "^3.0.2" -lru-cache@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-3.2.0.tgz#71789b3b7f5399bec8565dda38aa30d2a097efee" - integrity sha512-91gyOKTc2k66UG6kHiH4h3S2eltcPwE1STVfMYC/NG+nZwf8IIuiamfmpGZjpbbxzSyEJaLC0tNSmhjlQUTJow== - dependencies: - pseudomap "^1.0.1" - lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -6382,33 +3729,16 @@ lru_map@^0.3.3: resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" integrity sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ== -ltgt@^2.1.2, ltgt@~2.2.0: +ltgt@~2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5" integrity sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA== -ltgt@~2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.1.3.tgz#10851a06d9964b971178441c23c9e52698eece34" - integrity sha512-5VjHC5GsENtIi5rbJd+feEpDKhfr7j0odoUR2Uh978g+2p93nd5o34cTjQWohXsPsCZeqoDnIqEf88mPCe0Pfw== - make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w== - dependencies: - object-visit "^1.0.0" - markdown-table@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.3.tgz#9fcb69bcfdb8717bfd0398c6ec2d93036ef8de60" @@ -6433,23 +3763,6 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memdown@^1.0.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/memdown/-/memdown-1.4.1.tgz#b4e4e192174664ffbae41361aa500f3119efe215" - integrity sha512-iVrGHZB8i4OQfM155xx8akvG9FIj+ht14DX5CQkCTG4EHzZ3d3sgckIf/Lm9ivZalEsFuEVnWv2B2WZvbrro2w== - dependencies: - abstract-leveldown "~2.7.1" - functional-red-black-tree "^1.0.1" - immediate "^3.2.3" - inherits "~2.0.1" - ltgt "~2.2.0" - safe-buffer "~5.1.1" - memdown@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/memdown/-/memdown-5.1.0.tgz#608e91a9f10f37f5b5fe767667a8674129a833cb" @@ -6462,28 +3775,11 @@ memdown@^5.0.0: ltgt "~2.2.0" safe-buffer "~5.2.0" -memdown@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/memdown/-/memdown-3.0.0.tgz#93aca055d743b20efc37492e9e399784f2958309" - integrity sha512-tbV02LfZMWLcHcq4tw++NuqMO+FZX8tNJEiD2aNRm48ZZusVg5N8NART+dmBkepJVye986oixErf7jfXboMGMA== - dependencies: - abstract-leveldown "~5.0.0" - functional-red-black-tree "~1.0.1" - immediate "~3.2.3" - inherits "~2.0.1" - ltgt "~2.2.0" - safe-buffer "~5.1.1" - memorystream@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw== -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== - merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -6494,33 +3790,6 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -merkle-patricia-tree@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/merkle-patricia-tree/-/merkle-patricia-tree-3.0.0.tgz#448d85415565df72febc33ca362b8b614f5a58f8" - integrity sha512-soRaMuNf/ILmw3KWbybaCjhx86EYeBbD8ph0edQCTed0JN/rxDt1EBN52Ajre3VyGo+91f8+/rfPIRQnnGMqmQ== - dependencies: - async "^2.6.1" - ethereumjs-util "^5.2.0" - level-mem "^3.0.1" - level-ws "^1.0.0" - readable-stream "^3.0.6" - rlp "^2.0.0" - semaphore ">=1.0.1" - -merkle-patricia-tree@^2.1.2, merkle-patricia-tree@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/merkle-patricia-tree/-/merkle-patricia-tree-2.3.2.tgz#982ca1b5a0fde00eed2f6aeed1f9152860b8208a" - integrity sha512-81PW5m8oz/pz3GvsAwbauj7Y00rqm81Tzad77tHBwU7pIAtN+TJnMSOJhxBKflSVYhptMMb9RskhqHqrSm1V+g== - dependencies: - async "^1.4.2" - ethereumjs-util "^5.0.0" - level-ws "0.0.0" - levelup "^1.2.1" - memdown "^1.0.0" - readable-stream "^2.0.0" - rlp "^2.0.0" - semaphore ">=1.0.1" - merkle-patricia-tree@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/merkle-patricia-tree/-/merkle-patricia-tree-4.2.4.tgz#ff988d045e2bf3dfa2239f7fabe2d59618d57413" @@ -6533,30 +3802,6 @@ merkle-patricia-tree@^4.2.4: readable-stream "^3.6.0" semaphore-async-await "^1.5.1" -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" @@ -6578,18 +3823,13 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@^2.1.16, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -6605,18 +3845,6 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== -mimic-response@^1.0.0, mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - -min-document@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" - integrity sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ== - dependencies: - dom-walk "^0.1.0" - minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -6648,46 +3876,11 @@ minimatch@^3.0.4, minimatch@^3.1.1: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@~1.2.6: +minimist@^1.2.5, minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== -minipass@^2.6.0, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp-promise@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1" - integrity sha512-Hepn5kb1lJPtVW84RFT40YG1OddBNTOVUZR2bzQUHc+Z03en8/3uX0+060JDhcEzyO08HmipsN9DcnFMxhIL9w== - dependencies: - mkdirp "*" - -mkdirp@*, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - mkdirp@0.5.5: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -6695,13 +3888,18 @@ mkdirp@0.5.5: dependencies: minimist "^1.2.5" -mkdirp@^0.5.1, mkdirp@^0.5.5: +mkdirp@^0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== dependencies: minimist "^1.2.6" +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + mnemonist@^0.38.0: version "0.38.5" resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.38.5.tgz#4adc7f4200491237fe0fa689ac0b86539685cade" @@ -6767,16 +3965,6 @@ mocha@^7.1.1: yargs-parser "13.1.2" yargs-unparser "1.6.0" -mock-fs@^4.1.0: - version "4.14.0" - resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18" - integrity sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - ms@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" @@ -6792,46 +3980,6 @@ ms@2.1.3, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multibase@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/multibase/-/multibase-0.7.0.tgz#1adfc1c50abe05eefeb5091ac0c2728d6b84581b" - integrity sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg== - dependencies: - base-x "^3.0.8" - buffer "^5.5.0" - -multibase@~0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/multibase/-/multibase-0.6.1.tgz#b76df6298536cc17b9f6a6db53ec88f85f8cc12b" - integrity sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw== - dependencies: - base-x "^3.0.8" - buffer "^5.5.0" - -multicodec@^0.5.5: - version "0.5.7" - resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-0.5.7.tgz#1fb3f9dd866a10a55d226e194abba2dcc1ee9ffd" - integrity sha512-PscoRxm3f+88fAtELwUnZxGDkduE2HD9Q6GHUOywQLjOGT/HAdhjLDYNZ1e7VR0s0TP0EwZ16LNUTFpoBGivOA== - dependencies: - varint "^5.0.0" - -multicodec@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-1.0.4.tgz#46ac064657c40380c28367c90304d8ed175a714f" - integrity sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg== - dependencies: - buffer "^5.6.0" - varint "^5.0.0" - -multihashes@^0.4.15, multihashes@~0.4.15: - version "0.4.21" - resolved "https://registry.yarnpkg.com/multihashes/-/multihashes-0.4.21.tgz#dc02d525579f334a7909ade8a122dabb58ccfcb5" - integrity sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw== - dependencies: - buffer "^5.5.0" - multibase "^0.7.0" - varint "^5.0.0" - murmur-128@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/murmur-128/-/murmur-128-0.2.1.tgz#a9f6568781d2350ecb1bf80c14968cadbeaa4b4d" @@ -6846,53 +3994,21 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ== -nano-json-stream-parser@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f" - integrity sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew== - nanoid@3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - neo-async@^2.6.0: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -next-tick@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" - integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== - nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -6911,46 +4027,16 @@ node-environment-flags@1.0.6: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" -node-fetch@^2.6.1, node-fetch@^2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - -node-fetch@~1.7.1: - version "1.7.3" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" - integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - -node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: +node-gyp-build@^4.2.0: version "4.5.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40" integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg== -normalize-package-data@^2.3.2: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^4.1.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" - integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== - npm-run-path@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" @@ -6958,68 +4044,26 @@ npm-run-path@^5.1.0: dependencies: path-key "^4.0.0" -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== - -number-to-bn@1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" - integrity sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig== - dependencies: - bn.js "4.11.6" - strip-hex-prefix "1.0.0" - oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4, object-assign@^4.0.0, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ== - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-inspect@^1.12.0, object-inspect@^1.12.2, object-inspect@^1.9.0, object-inspect@~1.12.2: +object-inspect@^1.12.0, object-inspect@^1.12.2, object-inspect@^1.9.0: version "1.12.2" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== -object-is@^1.0.1: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" - integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - object-keys@^1.0.11, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-keys@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" - integrity sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw== - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA== - dependencies: - isobject "^3.0.0" - object.assign@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" @@ -7040,7 +4084,7 @@ object.assign@^4.1.2: has-symbols "^1.0.3" object-keys "^1.1.1" -object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.1: +object.getownpropertydescriptors@^2.0.3: version "2.1.4" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz#7965e6437a57278b587383831a9b829455a4bc37" integrity sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ== @@ -7050,33 +4094,12 @@ object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.1 define-properties "^1.1.4" es-abstract "^1.20.1" -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== - dependencies: - isobject "^3.0.1" - obliterator@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-2.0.4.tgz#fa650e019b2d075d745e44f1effeb13a2adbe816" integrity sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ== -oboe@2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/oboe/-/oboe-2.1.4.tgz#20c88cdb0c15371bb04119257d4fdd34b0aa49f6" - integrity sha512-ymBJ4xSC6GBXLT9Y7lirj+xbqBLa+jADGJldGEYG7u8sZbS9GyG+u1Xk9c5cbriKwSpCg41qUhPjvU5xOpvIyQ== - dependencies: - http-https "^1.0.0" - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -7104,14 +4127,6 @@ onetime@^6.0.0: dependencies: mimic-fn "^4.0.0" -open@^7.4.2: - version "7.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" - integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - optionator@^0.8.2: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -7124,38 +4139,16 @@ optionator@^0.8.2: type-check "~0.3.2" word-wrap "~1.2.3" -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== - -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - integrity sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g== - dependencies: - lcid "^1.0.0" +ordinal@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ordinal/-/ordinal-1.0.3.tgz#1a3c7726a61728112f50944ad7c35c06ae3a0d4d" + integrity sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ== -os-tmpdir@^1.0.1, os-tmpdir@~1.0.2: +os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== -p-cancelable@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" - integrity sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw== - -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -7205,13 +4198,6 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" -p-timeout@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" - integrity sha512-gb0ryzr+K2qFqFv6qi3khoeqMZF/+ajxQipEF6NteZVnvz9tzdsfAVj3lYtn1gAXvH5lfLwfxEII799gt/mRIA== - dependencies: - p-finally "^1.0.0" - p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -7229,34 +4215,11 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-asn1@^5.0.0, parse-asn1@^5.1.5: - version "5.1.6" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" - integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== - dependencies: - asn1.js "^5.2.0" - browserify-aes "^1.0.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - safe-buffer "^5.1.1" - parse-cache-control@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/parse-cache-control/-/parse-cache-control-1.0.1.tgz#8eeab3e54fa56920fe16ba38f77fa21aacc2d74e" integrity sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg== -parse-headers@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9" - integrity sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA== - -parse-json@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" - integrity sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ== - dependencies: - error-ex "^1.2.0" - parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" @@ -7265,65 +4228,6 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== - -patch-package@6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.2.2.tgz#71d170d650c65c26556f0d0fbbb48d92b6cc5f39" - integrity sha512-YqScVYkVcClUY0v8fF0kWOjDYopzIM8e3bj/RU1DPeEF14+dCGm6UeOYm4jvCyxqIEQ5/eJzmbWfDWnUleFNMg== - dependencies: - "@yarnpkg/lockfile" "^1.1.0" - chalk "^2.4.2" - cross-spawn "^6.0.5" - find-yarn-workspace-root "^1.2.1" - fs-extra "^7.0.1" - is-ci "^2.0.0" - klaw-sync "^6.0.0" - minimist "^1.2.0" - rimraf "^2.6.3" - semver "^5.6.0" - slash "^2.0.0" - tmp "^0.0.33" - -patch-package@^6.2.2: - version "6.4.7" - resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" - integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ== - dependencies: - "@yarnpkg/lockfile" "^1.1.0" - chalk "^2.4.2" - cross-spawn "^6.0.5" - find-yarn-workspace-root "^2.0.0" - fs-extra "^7.0.1" - is-ci "^2.0.0" - klaw-sync "^6.0.0" - minimist "^1.2.0" - open "^7.4.2" - rimraf "^2.6.3" - semver "^5.6.0" - slash "^2.0.0" - tmp "^0.0.33" - -path-browserify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" - integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== - -path-exists@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" - integrity sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ== - dependencies: - pinkie-promise "^2.0.0" - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -7334,7 +4238,7 @@ path-exists@^4.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: +path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== @@ -7359,25 +4263,11 @@ path-key@^4.0.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== -path-parse@^1.0.6, path-parse@^1.0.7: +path-parse@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - -path-type@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" - integrity sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg== - dependencies: - graceful-fs "^4.1.2" - pify "^2.0.0" - pinkie-promise "^2.0.0" - path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -7388,7 +4278,7 @@ pathval@^1.1.1: resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== -pbkdf2@^3.0.17, pbkdf2@^3.0.3, pbkdf2@^3.0.9: +pbkdf2@^3.0.17: version "3.1.2" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== @@ -7414,53 +4304,11 @@ pidtree@^0.6.0: resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== -pify@^2.0.0, pify@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== - -postinstall-postinstall@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" - integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== - -precond@0.2: - version "0.2.3" - resolved "https://registry.yarnpkg.com/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac" - integrity sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ== - prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== -prepend-http@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg== - -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== - prettier-plugin-solidity@^1.0.0-beta.19: version "1.0.0-beta.24" resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.0.0-beta.24.tgz#67573ca87098c14f7ccff3639ddd8a4cab2a87eb" @@ -7483,34 +4331,16 @@ prettier@^2.1.2, prettier@^2.5.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== -private@^0.1.6, private@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" - integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== - process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== - progress@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -promise-to-callback@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/promise-to-callback/-/promise-to-callback-1.0.0.tgz#5d2a749010bfb67d963598fcd3960746a68feef7" - integrity sha512-uhMIZmKM5ZteDMfLgJnoSq9GCwsNKrYau73Awf1jIy6/eUcuuZ3P+CD9zUv0kJsIUbU+x6uLNIhXhLHDs1pNPA== - dependencies: - is-fn "^1.0.0" - set-immediate-shim "^1.0.1" - promise@^8.0.0: version "8.1.0" resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" @@ -7518,119 +4348,21 @@ promise@^8.0.0: dependencies: asap "~2.0.6" -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== -pseudomap@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== - psl@^1.1.28: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== -public-encrypt@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" - integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - safe-buffer "^5.1.2" - -pull-cat@^1.1.9: - version "1.1.11" - resolved "https://registry.yarnpkg.com/pull-cat/-/pull-cat-1.1.11.tgz#b642dd1255da376a706b6db4fa962f5fdb74c31b" - integrity sha512-i3w+xZ3DCtTVz8S62hBOuNLRHqVDsHMNZmgrZsjPnsxXUgbWtXEee84lo1XswE7W2a3WHyqsNuDJTjVLAQR8xg== - -pull-defer@^0.2.2: - version "0.2.3" - resolved "https://registry.yarnpkg.com/pull-defer/-/pull-defer-0.2.3.tgz#4ee09c6d9e227bede9938db80391c3dac489d113" - integrity sha512-/An3KE7mVjZCqNhZsr22k1Tx8MACnUnHZZNPSJ0S62td8JtYr/AiRG42Vz7Syu31SoTLUzVIe61jtT/pNdjVYA== - -pull-level@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pull-level/-/pull-level-2.0.4.tgz#4822e61757c10bdcc7cf4a03af04c92734c9afac" - integrity sha512-fW6pljDeUThpq5KXwKbRG3X7Ogk3vc75d5OQU/TvXXui65ykm+Bn+fiktg+MOx2jJ85cd+sheufPL+rw9QSVZg== - dependencies: - level-post "^1.0.7" - pull-cat "^1.1.9" - pull-live "^1.0.1" - pull-pushable "^2.0.0" - pull-stream "^3.4.0" - pull-window "^2.1.4" - stream-to-pull-stream "^1.7.1" - -pull-live@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/pull-live/-/pull-live-1.0.1.tgz#a4ecee01e330155e9124bbbcf4761f21b38f51f5" - integrity sha512-tkNz1QT5gId8aPhV5+dmwoIiA1nmfDOzJDlOOUpU5DNusj6neNd3EePybJ5+sITr2FwyCs/FVpx74YMCfc8YeA== - dependencies: - pull-cat "^1.1.9" - pull-stream "^3.4.0" - -pull-pushable@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/pull-pushable/-/pull-pushable-2.2.0.tgz#5f2f3aed47ad86919f01b12a2e99d6f1bd776581" - integrity sha512-M7dp95enQ2kaHvfCt2+DJfyzgCSpWVR2h2kWYnVsW6ZpxQBx5wOu0QWOvQPVoPnBLUZYitYP2y7HyHkLQNeGXg== - -pull-stream@^3.2.3, pull-stream@^3.4.0, pull-stream@^3.6.8: - version "3.6.14" - resolved "https://registry.yarnpkg.com/pull-stream/-/pull-stream-3.6.14.tgz#529dbd5b86131f4a5ed636fdf7f6af00781357ee" - integrity sha512-KIqdvpqHHaTUA2mCYcLG1ibEbu/LCKoJZsBWyv9lSYtPkJPBq8m3Hxa103xHi6D2thj5YXa0TqK3L3GUkwgnew== - -pull-window@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/pull-window/-/pull-window-2.1.4.tgz#fc3b86feebd1920c7ae297691e23f705f88552f0" - integrity sha512-cbDzN76BMlcGG46OImrgpkMf/VkCnupj8JhsrpBw3aWBM9ye345aYnqitmZCgauBkc0HbbRRn9hCnsa3k2FNUg== - dependencies: - looper "^2.0.0" - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== - -punycode@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" - integrity sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA== - punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -qs@6.10.3: - version "6.10.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" - integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== - dependencies: - side-channel "^1.0.4" - qs@^6.4.0, qs@^6.7.0, qs@^6.9.4: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -7643,46 +4375,19 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== -query-string@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" - integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== - dependencies: - decode-uri-component "^0.2.0" - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.0.6, randombytes@^2.1.0: +randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.1, raw-body@^2.4.1: +raw-body@^2.4.1: version "2.5.1" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== @@ -7692,34 +4397,7 @@ raw-body@2.5.1, raw-body@^2.4.1: iconv-lite "0.4.24" unpipe "1.0.0" -read-pkg-up@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" - integrity sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A== - dependencies: - find-up "^1.0.0" - read-pkg "^1.0.0" - -read-pkg@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" - integrity sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ== - dependencies: - load-json-file "^1.0.0" - normalize-package-data "^2.3.2" - path-type "^1.0.0" - -readable-stream@^1.0.33: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - integrity sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.2.2, readable-stream@^2.2.8, readable-stream@^2.2.9, readable-stream@^2.3.6, readable-stream@~2.3.6: +readable-stream@^2.2.2: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -7732,7 +4410,7 @@ readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.2.2, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6, readable-stream@^3.1.0, readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@^3.1.0, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -7741,16 +4419,6 @@ readable-stream@^3.0.6, readable-stream@^3.1.0, readable-stream@^3.4.0, readable string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@~1.0.15: - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - integrity sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - readdirp@~3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" @@ -7770,34 +4438,7 @@ reduce-flatten@^2.0.0: resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== -regenerate@^1.2.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" - integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== - -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - -regenerator-transform@^0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" - integrity sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q== - dependencies: - babel-runtime "^6.18.0" - babel-types "^6.19.0" - private "^0.1.6" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.3: +regexp.prototype.flags@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== @@ -7811,44 +4452,6 @@ regexpp@^2.0.1: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== -regexpu-core@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" - integrity sha512-tJ9+S4oKjxY8IZ9jmjnp/mtytu1u3iyIQAfmI51IKWH6bFf7XR1ybtaO6j7INhZKXOTYADk7V5qxaqLkmNxiZQ== - dependencies: - regenerate "^1.2.1" - regjsgen "^0.2.0" - regjsparser "^0.1.4" - -regjsgen@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" - integrity sha512-x+Y3yA24uF68m5GA+tBjbGYo64xXVJpbToBaWCoSNSc1hdk6dfctaRWrNFTVJZIIhL5GxW8zwjoixbnifnK59g== - -regjsparser@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" - integrity sha512-jlQ9gYLfk2p3V5Ag5fYhA7fv7OHzd1KUH0PRP46xc3TgwjwgROIW572AfYg/X9kaNq/LJnu6oJcFRXlIrGoTRw== - dependencies: - jsesc "~0.5.0" - -repeat-element@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" - integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== - -repeating@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" - integrity sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A== - dependencies: - is-finite "^1.0.0" - req-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/req-cwd/-/req-cwd-2.0.0.tgz#d4082b4d44598036640fb73ddea01ed53db49ebc" @@ -7879,7 +4482,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.79.0, request@^2.85.0, request@^2.88.0: +request@^2.88.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -7910,21 +4513,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -require-from-string@^1.1.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418" - integrity sha512-H7AkJWMobeskkttHyhTVtS0fxpFLjxhbfMa6Bk3wimP7sdPRGL3EyCg3sAQenFfAe+xQ+oAc85Nmtvq0ROM83Q== - require-from-string@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - integrity sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug== - require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -7940,11 +4533,6 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== - resolve@1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" @@ -7952,22 +4540,6 @@ resolve@1.17.0: dependencies: path-parse "^1.0.6" -resolve@^1.10.0, resolve@^1.8.1, resolve@~1.22.1: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== - dependencies: - is-core-module "^2.9.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ== - dependencies: - lowercase-keys "^1.0.0" - restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -7984,18 +4556,6 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" -resumer@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759" - integrity sha512-Fn9X8rX8yYF4m81rZCK/5VmrmsSbqS/i3rDLl6ZZHAXgC2nTAx3dhwG8q8odP/RmdLa2YrybDJaAMg+X1ajY3w== - dependencies: - through "~2.3.4" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -8013,7 +4573,7 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" -rimraf@^2.2.8, rimraf@^2.6.3: +rimraf@^2.2.8: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -8035,7 +4595,7 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rlp@^2.0.0, rlp@^2.2.1, rlp@^2.2.2, rlp@^2.2.3, rlp@^2.2.4: +rlp@^2.2.3, rlp@^2.2.4: version "2.2.7" resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf" integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ== @@ -8073,7 +4633,7 @@ rxjs@^7.5.5: dependencies: tslib "^2.1.0" -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -8083,21 +4643,7 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-event-emitter@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/safe-event-emitter/-/safe-event-emitter-1.0.1.tgz#5b692ef22329ed8f69fdce607e50ca734f6f20af" - integrity sha512-e1wFe99A91XYYxoQbcq2ZJUWurxEyP8vfz7A7vuUe1s95q8r5ebraVaA1BukYJcpM6V16ugWoD9vngi8Ccu5fg== - dependencies: - events "^3.0.0" - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== - dependencies: - ret "~0.1.10" - -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -8107,18 +4653,11 @@ scrypt-js@2.0.4: resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.4.tgz#32f8c5149f0797672e551c07e230f834b6af5f16" integrity sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw== -scrypt-js@3.0.1, scrypt-js@^3.0.0, scrypt-js@^3.0.1: +scrypt-js@3.0.1, scrypt-js@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== -scryptsy@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/scryptsy/-/scryptsy-1.2.1.tgz#a3225fa4b2524f802700761e2855bdf3b2d92163" - integrity sha512-aldIRgMozSJ/Gl6K6qmJZysRP82lz83Wb42vl4PWN8SaLFHIaOzLPc9nUUW2jQN88CuGm5q5HefJ9jZ3nWSmTw== - dependencies: - pbkdf2 "^3.0.3" - secp256k1@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" @@ -8128,22 +4667,12 @@ secp256k1@^4.0.1: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" -seedrandom@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.1.tgz#eb3dde015bcf55df05a233514e5df44ef9dce083" - integrity sha512-1/02Y/rUeU1CJBAGLebiC5Lbo5FnB22gQbIFFYTLkwvp1xdABZJH1sn4ZT1MzXmPpzv+Rf/Lu2NcsLJiK4rcDg== - semaphore-async-await@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/semaphore-async-await/-/semaphore-async-await-1.5.1.tgz#857bef5e3644601ca4b9570b87e9df5ca12974fa" integrity sha512-b/ptP11hETwYWpeilHXXQiV5UJNJl7ZWWooKRE5eBIYWoom6dZ0SluCIdCtKycsMtZgKWE01/qAw6jblw1YVhg== -semaphore@>=1.0.1, semaphore@^1.0.3, semaphore@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.1.0.tgz#aaad8b86b20fe8e9b32b16dc2ee682a8cd26a8aa" - integrity sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA== - -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0: +semver@^5.5.0, semver@^5.5.1, semver@^5.7.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -8160,30 +4689,6 @@ semver@^7.3.2, semver@^7.3.7: dependencies: lru-cache "^6.0.0" -semver@~5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" - integrity sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg== - -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - serialize-javascript@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" @@ -8191,47 +4696,11 @@ serialize-javascript@6.0.0: dependencies: randombytes "^2.1.0" -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - -servify@^0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/servify/-/servify-0.1.12.tgz#142ab7bee1f1d033b66d0707086085b17c06db95" - integrity sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw== - dependencies: - body-parser "^1.16.0" - cors "^2.8.1" - express "^4.14.0" - request "^2.79.0" - xhr "^2.3.3" - set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== -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" - integrity sha512-Li5AOqrZWCVA2n5kryzEmqai6bKSIvpz5oUJHPVj6+dsbD3X1ixtsY5tEnsaNpH3pFAHmG8eIHUrtEtohrg+UQ== - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - setimmediate@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.4.tgz#20e81de622d4a02588ce0c8da8973cbcf1d3138f" @@ -8301,30 +4770,6 @@ signal-exit@^3.0.2, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -simple-concat@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" - integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== - -simple-get@^2.7.0: - version "2.8.2" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.2.tgz#5708fb0919d440657326cd5fe7d2599d07705019" - integrity sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw== - dependencies: - decompress-response "^3.3.0" - once "^1.3.1" - simple-concat "^1.0.0" - -slash@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" - integrity sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg== - -slash@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" - integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -8365,36 +4810,6 @@ slice-ansi@^5.0.0: ansi-styles "^6.0.0" is-fullwidth-code-point "^4.0.0" -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - "solc-0.8@npm:solc@^0.8.0": version "0.8.16" resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.16.tgz#120f992357e236d99e6cf3445bf2c2dca3384f96" @@ -8423,18 +4838,7 @@ solc@0.7.3: semver "^5.5.0" tmp "0.0.33" -solc@^0.4.20: - version "0.4.26" - resolved "https://registry.yarnpkg.com/solc/-/solc-0.4.26.tgz#5390a62a99f40806b86258c737c1cf653cc35cb5" - integrity sha512-o+c6FpkiHd+HPjmjEVpQgH7fqZ14tJpXhho+/bQXlXbliLIS/xjXb42Vxh+qQY1WCSTMQ0+a5vR9vi0MfhU6mA== - dependencies: - fs-extra "^0.30.0" - memorystream "^0.3.1" - require-from-string "^1.1.0" - semver "^5.3.0" - yargs "^4.7.1" - -solc@^0.6.3, solc@^0.6.7: +solc@^0.6.7: version "0.6.12" resolved "https://registry.yarnpkg.com/solc/-/solc-0.6.12.tgz#48ac854e0c729361b22a7483645077f58cba080e" integrity sha512-Lm0Ql2G9Qc7yPP2Ba+WNmzw2jwsrd3u4PobHYlSOxaut3TtUbj9+5ZrT6f4DUpNPEoBaFUOEg9Op9C0mk7ge9g== @@ -8493,32 +4897,6 @@ solidity-docgen@0.5.16: semver "^7.3.2" solc "^0.6.7" -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-support@0.5.12: - version "0.5.12" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" - integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-support@^0.4.15: - version "0.4.18" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" - integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== - dependencies: - source-map "^0.5.6" - source-map-support@^0.5.13: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" @@ -8527,54 +4905,11 @@ source-map-support@^0.5.13: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-url@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" - integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== - -source-map@^0.5.6, source-map@^0.5.7: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== - source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.11" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" - integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -8602,14 +4937,6 @@ stacktrace-parser@^0.1.10: dependencies: type-fest "^0.7.1" -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g== - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" @@ -8620,19 +4947,6 @@ stealthy-require@^1.1.1: resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g== -stream-to-pull-stream@^1.7.1: - version "1.7.3" - resolved "https://registry.yarnpkg.com/stream-to-pull-stream/-/stream-to-pull-stream-1.7.3.tgz#4161aa2d2eb9964de60bfa1af7feaf917e874ece" - integrity sha512-6sNyqJpr5dIOQdgNy/xcDWwDuzAsAwVzhzrWlAPAQ7Lkjx/rv0wgvxEyKwTq6FmNd5rjTrELt/CLmaSw7crMGg== - dependencies: - looper "^3.0.0" - pull-stream "^3.2.3" - -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ== - string-argv@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" @@ -8643,15 +4957,6 @@ string-format@^2.0.0: resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw== - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - "string-width@^1.0.2 || 2", string-width@^2.1.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" @@ -8687,15 +4992,6 @@ string-width@^5.0.0: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string.prototype.trim@~1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.6.tgz#824960787db37a9e24711802ed0c1d1c0254f83e" - integrity sha512-8lMR2m+U0VJTPp6JjvJTtGyc4FIGq9CdRt7O9p6T0e6K4vjU+OP+SQJpbe/SBmRcCUIvNUnjsbmY6lnMp8MhsQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.19.5" - string.prototype.trimend@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" @@ -8721,11 +5017,6 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -8733,13 +5024,6 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== - dependencies: - ansi-regex "^2.0.0" - strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -8768,13 +5052,6 @@ strip-ansi@^7.0.1: dependencies: ansi-regex "^6.0.1" -strip-bom@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" - integrity sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g== - dependencies: - is-utf8 "^0.2.0" - strip-final-newline@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" @@ -8811,11 +5088,6 @@ supports-color@8.1.1: dependencies: has-flag "^4.0.0" -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== - supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -8830,28 +5102,6 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -swarm-js@^0.1.40: - version "0.1.40" - resolved "https://registry.yarnpkg.com/swarm-js/-/swarm-js-0.1.40.tgz#b1bc7b6dcc76061f6c772203e004c11997e06b99" - integrity sha512-yqiOCEoA4/IShXkY3WKwP5PvZhmoOOD8clsKA7EEcRILMkTEYHCQ21HDCAcVpmIxZq4LyZvWeRJ6quIyHk1caA== - dependencies: - bluebird "^3.5.0" - buffer "^5.0.5" - eth-lib "^0.1.26" - fs-extra "^4.0.2" - got "^7.1.0" - mime-types "^2.1.16" - mkdirp-promise "^5.0.1" - mock-fs "^4.1.0" - setimmediate "^1.0.5" - tar "^4.0.2" - xhr-request "^1.0.1" - sync-request@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-6.1.0.tgz#e96217565b5e50bbffe179868ba75532fb597e68" @@ -8888,53 +5138,6 @@ table@^5.2.3: slice-ansi "^2.1.0" string-width "^3.0.0" -tape@^4.6.3: - version "4.16.0" - resolved "https://registry.yarnpkg.com/tape/-/tape-4.16.0.tgz#18310f57b71c0ac21b3ef94fe5c16033b3d6362b" - integrity sha512-mBlqYFr2mHysgCFXAuSarIQ+ffhielpb7a5/IbeOhMaLnQYhkJLUm6CwO1RszWeHRxnIpMessZ3xL2Cfo94BWw== - dependencies: - call-bind "~1.0.2" - deep-equal "~1.1.1" - defined "~1.0.0" - dotignore "~0.1.2" - for-each "~0.3.3" - glob "~7.2.3" - has "~1.0.3" - inherits "~2.0.4" - is-regex "~1.1.4" - minimist "~1.2.6" - object-inspect "~1.12.2" - resolve "~1.22.1" - resumer "~0.0.0" - string.prototype.trim "~1.2.6" - through "~2.3.8" - -tar@^4.0.2: - version "4.4.19" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" - integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== - dependencies: - chownr "^1.1.4" - fs-minipass "^1.2.7" - minipass "^2.9.0" - minizlib "^1.3.3" - mkdirp "^0.5.5" - safe-buffer "^5.2.1" - yallist "^3.1.1" - -test-value@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/test-value/-/test-value-2.1.0.tgz#11da6ff670f3471a73b625ca4f3fdcf7bb748291" - integrity sha512-+1epbAxtKeXttkGFMTX9H42oqzOTufR1ceCF+GYA5aOmvaPq9wd4PUS8329fn2RRLGNeUkgRLnVpycjx8DsO2w== - dependencies: - array-back "^1.0.3" - typical "^2.6.0" - -testrpc@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/testrpc/-/testrpc-0.0.1.tgz#83e2195b1f5873aec7be1af8cbe6dcf39edb7aed" - integrity sha512-afH1hO+SQ/VPlmaLUFj2636QMeDvPCeQMc/9RBMW0IfjNe9gFD9Ra3ShqYkB7py0do1ZcCna/9acHyzTJ+GcNA== - text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -8957,24 +5160,11 @@ then-request@^6.0.0: promise "^8.0.0" qs "^6.4.0" -through2@^2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -through@^2.3.6, through@^2.3.8, through@~2.3.4, through@~2.3.8: +through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== -timed-out@^4.0.0, timed-out@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA== - tmp@0.0.33, tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -8982,38 +5172,6 @@ tmp@0.0.33, tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" -tmp@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" - integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== - dependencies: - rimraf "^2.6.3" - -to-fast-properties@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" - integrity sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og== - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg== - dependencies: - kind-of "^3.0.2" - -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg== - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -9021,16 +5179,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - toidentifier@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" @@ -9044,16 +5192,6 @@ tough-cookie@^2.3.3, tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - integrity sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw== - "true-case-path@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-2.2.1.tgz#c5bf04a5bbec3fd118be4084461b3a27c4d796bf" @@ -9069,36 +5207,11 @@ ts-command-line-args@^2.2.0: command-line-usage "^6.1.0" string-format "^2.0.0" -ts-essentials@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-1.0.4.tgz#ce3b5dade5f5d97cf69889c11bf7d2da8555b15a" - integrity sha512-q3N1xS4vZpRouhYHDPwO0bDW3EZ6SK9CrrDHxi/D6BPReSjpVgWIOpLS2o0gSBZm+7q/wyKp6RVM1AeeW7uyfQ== - -ts-essentials@^6.0.3: - version "6.0.7" - resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-6.0.7.tgz#5f4880911b7581a873783740ce8b94da163d18a6" - integrity sha512-2E4HIIj4tQJlIHuATRHayv0EfMGK3ris/GRk1E3CFnsZzeNV+hUmelbaTZHLtXaZppM5oLhHRtO04gINC4Jusw== - ts-essentials@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38" integrity sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ== -ts-generator@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ts-generator/-/ts-generator-0.1.1.tgz#af46f2fb88a6db1f9785977e9590e7bcd79220ab" - integrity sha512-N+ahhZxTLYu1HNTQetwWcx3so8hcYbkKBHTr4b4/YgObFTIKkOSSsaa+nal12w8mfrJAyzJfETXawbNjSfP2gQ== - dependencies: - "@types/mkdirp" "^0.5.2" - "@types/prettier" "^2.1.1" - "@types/resolve" "^0.0.8" - chalk "^2.4.1" - glob "^7.1.2" - mkdirp "^0.5.1" - prettier "^2.1.2" - resolve "^1.8.1" - ts-essentials "^1.0.0" - ts-node@^10.4.0: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" @@ -9140,7 +5253,7 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tweetnacl-util@^0.15.0, tweetnacl-util@^0.15.1: +tweetnacl-util@^0.15.1: version "0.15.1" resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== @@ -9150,7 +5263,7 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== -tweetnacl@^1.0.0, tweetnacl@^1.0.3: +tweetnacl@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== @@ -9177,37 +5290,6 @@ type-fest@^0.7.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -type@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" - integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== - -type@^2.5.0: - version "2.7.2" - resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" - integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== - -typechain@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/typechain/-/typechain-3.0.0.tgz#d5a47700831f238e43f7429b987b4bb54849b92e" - integrity sha512-ft4KVmiN3zH4JUFu2WJBrwfHeDf772Tt2d8bssDTo/YcckKW2D+OwFrHXRC6hJvO3mHjFQTihoMV6fJOi0Hngg== - dependencies: - command-line-args "^4.0.7" - debug "^4.1.1" - fs-extra "^7.0.0" - js-sha3 "^0.8.0" - lodash "^4.17.15" - ts-essentials "^6.0.3" - ts-generator "^0.1.1" - typechain@^6.0.5: version "6.1.0" resolved "https://registry.yarnpkg.com/typechain/-/typechain-6.1.0.tgz#462a35f555accf870689d1ba5698749108d0ce81" @@ -9224,13 +5306,6 @@ typechain@^6.0.5: ts-command-line-args "^2.2.0" ts-essentials "^7.0.1" -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -9241,28 +5316,6 @@ typescript@^4.5.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== -typewise-core@^1.2, typewise-core@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/typewise-core/-/typewise-core-1.2.0.tgz#97eb91805c7f55d2f941748fa50d315d991ef195" - integrity sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg== - -typewise@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/typewise/-/typewise-1.0.3.tgz#1067936540af97937cc5dcf9922486e9fa284651" - integrity sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ== - dependencies: - typewise-core "^1.2.0" - -typewiselite@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typewiselite/-/typewiselite-1.0.0.tgz#c8882fa1bb1092c06005a97f34ef5c8508e3664e" - integrity sha512-J9alhjVHupW3Wfz6qFRGgQw0N3gr8hOkw6zm7FZ6UR1Cse/oD9/JVok7DNE9TT9IbciDHX2Ex9+ksE6cRmtymw== - -typical@^2.6.0, typical@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d" - integrity sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg== - typical@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" @@ -9278,11 +5331,6 @@ uglify-js@^3.1.4: resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.0.tgz#55bd6e9d19ce5eef0d5ad17cd1f587d85b180a85" integrity sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg== -ultron@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" - integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== - unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -9293,26 +5341,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" -underscore@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" - integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== - undici@^5.4.0: version "5.9.1" resolved "https://registry.yarnpkg.com/undici/-/undici-5.9.1.tgz#fc9fd85dd488f965f153314a63d9426a11f3360b" integrity sha512-6fB3a+SNnWEm4CJbgo0/CWR8RGcOCQP68SF4X0mxtYTq2VNN8T88NYrWVBAeSX+zb7bny2dx2iYhP3XHi00omg== -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -9323,24 +5356,11 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -unorm@^1.3.3: - version "1.6.0" - resolved "https://registry.yarnpkg.com/unorm/-/unorm-1.6.0.tgz#029b289661fba714f1a9af439eb51d9b16c205af" - integrity sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA== - -unpipe@1.0.0, unpipe@~1.0.0: +unpipe@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ== - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -9348,91 +5368,16 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== - -url-parse-lax@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" - integrity sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA== - dependencies: - prepend-http "^1.0.1" - -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ== - dependencies: - prepend-http "^2.0.0" - -url-set-query@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/url-set-query/-/url-set-query-1.0.0.tgz#016e8cfd7c20ee05cafe7795e892bd0702faa339" - integrity sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg== - -url-to-options@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" - integrity sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A== - -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ== - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -utf-8-validate@^5.0.2: - version "5.0.9" - resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.9.tgz#ba16a822fbeedff1a58918f2a6a6b36387493ea3" - integrity sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q== - dependencies: - node-gyp-build "^4.3.0" - -utf8@3.0.0, utf8@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" - integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== - util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -util.promisify@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.1.1.tgz#77832f57ced2c9478174149cae9b96e9918cd54b" - integrity sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - for-each "^0.3.3" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.1" - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - uuid@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.1.tgz#c2a30dedb3e535d72ccf82e343941a50ba8533ac" integrity sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg== -uuid@3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== - uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" @@ -9448,24 +5393,6 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -varint@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/varint/-/varint-5.0.2.tgz#5b47f8a947eb668b848e034dcfa87d0ff8a7f7a4" - integrity sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow== - -vary@^1, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -9475,326 +5402,6 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -web3-bzz@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.2.11.tgz#41bc19a77444bd5365744596d778b811880f707f" - integrity sha512-XGpWUEElGypBjeFyUhTkiPXFbDVD6Nr/S5jznE3t8cWUA0FxRf1n3n/NuIZeb0H9RkN2Ctd/jNma/k8XGa3YKg== - dependencies: - "@types/node" "^12.12.6" - got "9.6.0" - swarm-js "^0.1.40" - underscore "1.9.1" - -web3-core-helpers@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.2.11.tgz#84c681ed0b942c0203f3b324a245a127e8c67a99" - integrity sha512-PEPoAoZd5ME7UfbnCZBdzIerpe74GEvlwT4AjOmHeCVZoIFk7EqvOZDejJHt+feJA6kMVTdd0xzRNN295UhC1A== - dependencies: - underscore "1.9.1" - web3-eth-iban "1.2.11" - web3-utils "1.2.11" - -web3-core-method@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.2.11.tgz#f880137d1507a0124912bf052534f168b8d8fbb6" - integrity sha512-ff0q76Cde94HAxLDZ6DbdmKniYCQVtvuaYh+rtOUMB6kssa5FX0q3vPmixi7NPooFnbKmmZCM6NvXg4IreTPIw== - dependencies: - "@ethersproject/transactions" "^5.0.0-beta.135" - underscore "1.9.1" - web3-core-helpers "1.2.11" - web3-core-promievent "1.2.11" - web3-core-subscriptions "1.2.11" - web3-utils "1.2.11" - -web3-core-promievent@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.2.11.tgz#51fe97ca0ddec2f99bf8c3306a7a8e4b094ea3cf" - integrity sha512-il4McoDa/Ox9Agh4kyfQ8Ak/9ABYpnF8poBLL33R/EnxLsJOGQG2nZhkJa3I067hocrPSjEdlPt/0bHXsln4qA== - dependencies: - eventemitter3 "4.0.4" - -web3-core-requestmanager@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.2.11.tgz#fe6eb603fbaee18530293a91f8cf26d8ae28c45a" - integrity sha512-oFhBtLfOiIbmfl6T6gYjjj9igOvtyxJ+fjS+byRxiwFJyJ5BQOz4/9/17gWR1Cq74paTlI7vDGxYfuvfE/mKvA== - dependencies: - underscore "1.9.1" - web3-core-helpers "1.2.11" - web3-providers-http "1.2.11" - web3-providers-ipc "1.2.11" - web3-providers-ws "1.2.11" - -web3-core-subscriptions@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.2.11.tgz#beca908fbfcb050c16f45f3f0f4c205e8505accd" - integrity sha512-qEF/OVqkCvQ7MPs1JylIZCZkin0aKK9lDxpAtQ1F8niEDGFqn7DT8E/vzbIa0GsOjL2fZjDhWJsaW+BSoAW1gg== - dependencies: - eventemitter3 "4.0.4" - underscore "1.9.1" - web3-core-helpers "1.2.11" - -web3-core@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.2.11.tgz#1043cacc1becb80638453cc5b2a14be9050288a7" - integrity sha512-CN7MEYOY5ryo5iVleIWRE3a3cZqVaLlIbIzDPsvQRUfzYnvzZQRZBm9Mq+ttDi2STOOzc1MKylspz/o3yq/LjQ== - dependencies: - "@types/bn.js" "^4.11.5" - "@types/node" "^12.12.6" - bignumber.js "^9.0.0" - web3-core-helpers "1.2.11" - web3-core-method "1.2.11" - web3-core-requestmanager "1.2.11" - web3-utils "1.2.11" - -web3-eth-abi@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.2.11.tgz#a887494e5d447c2926d557a3834edd66e17af9b0" - integrity sha512-PkRYc0+MjuLSgg03QVWqWlQivJqRwKItKtEpRUaxUAeLE7i/uU39gmzm2keHGcQXo3POXAbOnMqkDvOep89Crg== - dependencies: - "@ethersproject/abi" "5.0.0-beta.153" - underscore "1.9.1" - web3-utils "1.2.11" - -web3-eth-accounts@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.2.11.tgz#a9e3044da442d31903a7ce035a86d8fa33f90520" - integrity sha512-6FwPqEpCfKIh3nSSGeo3uBm2iFSnFJDfwL3oS9pyegRBXNsGRVpgiW63yhNzL0796StsvjHWwQnQHsZNxWAkGw== - dependencies: - crypto-browserify "3.12.0" - eth-lib "0.2.8" - ethereumjs-common "^1.3.2" - ethereumjs-tx "^2.1.1" - scrypt-js "^3.0.1" - underscore "1.9.1" - uuid "3.3.2" - web3-core "1.2.11" - web3-core-helpers "1.2.11" - web3-core-method "1.2.11" - web3-utils "1.2.11" - -web3-eth-contract@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.2.11.tgz#917065902bc27ce89da9a1da26e62ef663663b90" - integrity sha512-MzYuI/Rq2o6gn7vCGcnQgco63isPNK5lMAan2E51AJLknjSLnOxwNY3gM8BcKoy4Z+v5Dv00a03Xuk78JowFow== - dependencies: - "@types/bn.js" "^4.11.5" - underscore "1.9.1" - web3-core "1.2.11" - web3-core-helpers "1.2.11" - web3-core-method "1.2.11" - web3-core-promievent "1.2.11" - web3-core-subscriptions "1.2.11" - web3-eth-abi "1.2.11" - web3-utils "1.2.11" - -web3-eth-ens@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.2.11.tgz#26d4d7f16d6cbcfff918e39832b939edc3162532" - integrity sha512-dbW7dXP6HqT1EAPvnniZVnmw6TmQEKF6/1KgAxbo8iBBYrVTMDGFQUUnZ+C4VETGrwwaqtX4L9d/FrQhZ6SUiA== - dependencies: - content-hash "^2.5.2" - eth-ens-namehash "2.0.8" - underscore "1.9.1" - web3-core "1.2.11" - web3-core-helpers "1.2.11" - web3-core-promievent "1.2.11" - web3-eth-abi "1.2.11" - web3-eth-contract "1.2.11" - web3-utils "1.2.11" - -web3-eth-iban@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.2.11.tgz#f5f73298305bc7392e2f188bf38a7362b42144ef" - integrity sha512-ozuVlZ5jwFC2hJY4+fH9pIcuH1xP0HEFhtWsR69u9uDIANHLPQQtWYmdj7xQ3p2YT4bQLq/axKhZi7EZVetmxQ== - dependencies: - bn.js "^4.11.9" - web3-utils "1.2.11" - -web3-eth-personal@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.2.11.tgz#a38b3942a1d87a62070ce0622a941553c3d5aa70" - integrity sha512-42IzUtKq9iHZ8K9VN0vAI50iSU9tOA1V7XU2BhF/tb7We2iKBVdkley2fg26TxlOcKNEHm7o6HRtiiFsVK4Ifw== - dependencies: - "@types/node" "^12.12.6" - web3-core "1.2.11" - web3-core-helpers "1.2.11" - web3-core-method "1.2.11" - web3-net "1.2.11" - web3-utils "1.2.11" - -web3-eth@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.2.11.tgz#4c81fcb6285b8caf544058fba3ae802968fdc793" - integrity sha512-REvxW1wJ58AgHPcXPJOL49d1K/dPmuw4LjPLBPStOVkQjzDTVmJEIsiLwn2YeuNDd4pfakBwT8L3bz1G1/wVsQ== - dependencies: - underscore "1.9.1" - web3-core "1.2.11" - web3-core-helpers "1.2.11" - web3-core-method "1.2.11" - web3-core-subscriptions "1.2.11" - web3-eth-abi "1.2.11" - web3-eth-accounts "1.2.11" - web3-eth-contract "1.2.11" - web3-eth-ens "1.2.11" - web3-eth-iban "1.2.11" - web3-eth-personal "1.2.11" - web3-net "1.2.11" - web3-utils "1.2.11" - -web3-net@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.2.11.tgz#eda68ef25e5cdb64c96c39085cdb74669aabbe1b" - integrity sha512-sjrSDj0pTfZouR5BSTItCuZ5K/oZPVdVciPQ6981PPPIwJJkCMeVjD7I4zO3qDPCnBjBSbWvVnLdwqUBPtHxyg== - dependencies: - web3-core "1.2.11" - web3-core-method "1.2.11" - web3-utils "1.2.11" - -web3-provider-engine@14.2.1: - version "14.2.1" - resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-14.2.1.tgz#ef351578797bf170e08d529cb5b02f8751329b95" - integrity sha512-iSv31h2qXkr9vrL6UZDm4leZMc32SjWJFGOp/D92JXfcEboCqraZyuExDkpxKw8ziTufXieNM7LSXNHzszYdJw== - dependencies: - async "^2.5.0" - backoff "^2.5.0" - clone "^2.0.0" - cross-fetch "^2.1.0" - eth-block-tracker "^3.0.0" - eth-json-rpc-infura "^3.1.0" - eth-sig-util "^1.4.2" - ethereumjs-block "^1.2.2" - ethereumjs-tx "^1.2.0" - ethereumjs-util "^5.1.5" - ethereumjs-vm "^2.3.4" - json-rpc-error "^2.0.0" - json-stable-stringify "^1.0.1" - promise-to-callback "^1.0.0" - readable-stream "^2.2.9" - request "^2.85.0" - semaphore "^1.0.3" - ws "^5.1.1" - xhr "^2.2.0" - xtend "^4.0.1" - -web3-providers-http@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.2.11.tgz#1cd03442c61670572d40e4dcdf1faff8bd91e7c6" - integrity sha512-psh4hYGb1+ijWywfwpB2cvvOIMISlR44F/rJtYkRmQ5jMvG4FOCPlQJPiHQZo+2cc3HbktvvSJzIhkWQJdmvrA== - dependencies: - web3-core-helpers "1.2.11" - xhr2-cookies "1.1.0" - -web3-providers-ipc@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.2.11.tgz#d16d6c9be1be6e0b4f4536c4acc16b0f4f27ef21" - integrity sha512-yhc7Y/k8hBV/KlELxynWjJDzmgDEDjIjBzXK+e0rHBsYEhdCNdIH5Psa456c+l0qTEU2YzycF8VAjYpWfPnBpQ== - dependencies: - oboe "2.1.4" - underscore "1.9.1" - web3-core-helpers "1.2.11" - -web3-providers-ws@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.2.11.tgz#a1dfd6d9778d840561d9ec13dd453046451a96bb" - integrity sha512-ZxnjIY1Er8Ty+cE4migzr43zA/+72AF1myzsLaU5eVgdsfV7Jqx7Dix1hbevNZDKFlSoEyq/3j/jYalh3So1Zg== - dependencies: - eventemitter3 "4.0.4" - underscore "1.9.1" - web3-core-helpers "1.2.11" - websocket "^1.0.31" - -web3-shh@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.2.11.tgz#f5d086f9621c9a47e98d438010385b5f059fd88f" - integrity sha512-B3OrO3oG1L+bv3E1sTwCx66injW1A8hhwpknDUbV+sw3fehFazA06z9SGXUefuFI1kVs4q2vRi0n4oCcI4dZDg== - dependencies: - web3-core "1.2.11" - web3-core-method "1.2.11" - web3-core-subscriptions "1.2.11" - web3-net "1.2.11" - -web3-utils@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.11.tgz#af1942aead3fb166ae851a985bed8ef2c2d95a82" - integrity sha512-3Tq09izhD+ThqHEaWYX4VOT7dNPdZiO+c/1QMA0s5X2lDFKK/xHJb7cyTRRVzN2LvlHbR7baS1tmQhSua51TcQ== - dependencies: - bn.js "^4.11.9" - eth-lib "0.2.8" - ethereum-bloom-filters "^1.0.6" - ethjs-unit "0.1.6" - number-to-bn "1.7.0" - randombytes "^2.1.0" - underscore "1.9.1" - utf8 "3.0.0" - -web3-utils@^1.0.0-beta.31: - version "1.7.5" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.7.5.tgz#081a952ac6e0322e25ac97b37358a43c7372ef6a" - integrity sha512-9AqNOziQky4wNQadEwEfHiBdOZqopIHzQQVzmvvv6fJwDSMhP+khqmAZC7YTiGjs0MboyZ8tWNivqSO1699XQw== - dependencies: - bn.js "^5.2.1" - ethereum-bloom-filters "^1.0.6" - ethereumjs-util "^7.1.0" - ethjs-unit "0.1.6" - number-to-bn "1.7.0" - randombytes "^2.1.0" - utf8 "3.0.0" - -web3@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/web3/-/web3-1.2.11.tgz#50f458b2e8b11aa37302071c170ed61cff332975" - integrity sha512-mjQ8HeU41G6hgOYm1pmeH0mRAeNKJGnJEUzDMoerkpw7QUQT4exVREgF1MYPvL/z6vAshOXei25LE/t/Bxl8yQ== - dependencies: - web3-bzz "1.2.11" - web3-core "1.2.11" - web3-eth "1.2.11" - web3-eth-personal "1.2.11" - web3-net "1.2.11" - web3-shh "1.2.11" - web3-utils "1.2.11" - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -websocket@1.0.32: - version "1.0.32" - resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.32.tgz#1f16ddab3a21a2d929dec1687ab21cfdc6d3dbb1" - integrity sha512-i4yhcllSP4wrpoPMU2N0TQ/q0O94LRG/eUQjEAamRltjQ1oT1PFFKOG4i877OlJgCG8rw6LrrowJp+TYCEWF7Q== - dependencies: - bufferutil "^4.0.1" - debug "^2.2.0" - es5-ext "^0.10.50" - typedarray-to-buffer "^3.1.5" - utf-8-validate "^5.0.2" - yaeti "^0.0.6" - -websocket@^1.0.31: - version "1.0.34" - resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111" - integrity sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ== - dependencies: - bufferutil "^4.0.1" - debug "^2.2.0" - es5-ext "^0.10.50" - typedarray-to-buffer "^3.1.5" - utf-8-validate "^5.0.2" - yaeti "^0.0.6" - -whatwg-fetch@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" - integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -9806,11 +5413,6 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" - integrity sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ== - which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" @@ -9844,11 +5446,6 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" -window-size@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" - integrity sha512-UD7d8HFA2+PZsbKyaOCEy8gMh1oDtHgJh1LfgjQ4zVXmYjAT/kvz3PueITKuqDiIXQe7yzpPnxX3lNc+AhQMyw== - word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" @@ -9872,14 +5469,6 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw== - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" @@ -9924,86 +5513,21 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== -ws@^3.0.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" - integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== - dependencies: - async-limiter "~1.0.0" - safe-buffer "~5.1.0" - ultron "~1.1.0" - -ws@^5.1.1: - version "5.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.3.tgz#05541053414921bc29c63bee14b8b0dd50b07b3d" - integrity sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA== - dependencies: - async-limiter "~1.0.0" - ws@^7.4.6: version "7.5.9" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -xhr-request-promise@^0.1.2: - version "0.1.3" - resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz#2d5f4b16d8c6c893be97f1a62b0ed4cf3ca5f96c" - integrity sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg== - dependencies: - xhr-request "^1.1.0" - -xhr-request@^1.0.1, xhr-request@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/xhr-request/-/xhr-request-1.1.0.tgz#f4a7c1868b9f198723444d82dcae317643f2e2ed" - integrity sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA== - dependencies: - buffer-to-arraybuffer "^0.0.5" - object-assign "^4.1.1" - query-string "^5.0.1" - simple-get "^2.7.0" - timed-out "^4.0.1" - url-set-query "^1.0.0" - xhr "^2.0.4" - -xhr2-cookies@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz#7d77449d0999197f155cb73b23df72505ed89d48" - integrity sha512-hjXUA6q+jl/bd8ADHcVfFsSPIf+tyLIjuO9TwJC9WI6JP2zKcS7C+p56I9kCLLsaCiNT035iYvEUUzdEFj/8+g== - dependencies: - cookiejar "^2.1.1" - -xhr@^2.0.4, xhr@^2.2.0, xhr@^2.3.3: - version "2.6.0" - resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.6.0.tgz#b69d4395e792b4173d6b7df077f0fc5e4e2b249d" - integrity sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA== - dependencies: - global "~4.4.0" - is-function "^1.0.1" - parse-headers "^2.0.0" - xtend "^4.0.0" - xmlhttprequest@1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" integrity sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA== -xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1: +xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xtend@~2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b" - integrity sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ== - dependencies: - object-keys "~0.4.0" - -y18n@^3.2.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" - integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== - y18n@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" @@ -10014,12 +5538,7 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yaeti@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" - integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug== - -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: +yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== @@ -10047,14 +5566,6 @@ yargs-parser@20.2.4: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== -yargs-parser@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4" - integrity sha512-9pIKIJhnI5tonzG6OnCFlz/yln8xHYcGl+pn3xR0Vzff0vzN1PbNRaelgfgRUwZ3s4i3jvxT9WhmUGL4whnasA== - dependencies: - camelcase "^3.0.0" - lodash.assign "^4.0.6" - yargs-parser@^20.2.2: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" @@ -10108,26 +5619,6 @@ yargs@16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^4.7.1: - version "4.8.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" - integrity sha512-LqodLrnIDM3IFT+Hf/5sxBnEGECrfdC1uIbgZeJmESCSo4HoCAaKEus8MylXHAkdacGc0ye+Qa+dpkuom8uVYA== - dependencies: - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - lodash.assign "^4.0.3" - os-locale "^1.4.0" - read-pkg-up "^1.0.1" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^1.0.1" - which-module "^1.0.0" - window-size "^0.2.0" - y18n "^3.2.1" - yargs-parser "^2.4.1" - yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From 3caa3e1c75cd78ce7601c03087e0942a2650a089 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 12 Sep 2022 15:37:17 +0700 Subject: [PATCH 113/190] [IntegrationTest] Fix submit reward test --- test/integration/ActionSubmitReward.test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index 17eab9fb2..283e2a436 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { network, ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs'; import { SlashIndicator, @@ -164,11 +165,13 @@ describe('[Integration] Submit Block Reward', () => { .withArgs(validator.address, blockRewardAmount); }); - it.skip('Should the StakingContract emit event of recording reward', async () => { - await expect(submitRewardTx).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(validator.address); + it.skip('Should the ValidatorSetContract update mining reward', async () => {}); + + it('Should the StakingContract emit event of recording reward', async () => { + await expect(submitRewardTx).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(validator.address, anyValue); }); - it('Should the StakingContract record update for new block reward', async () => {}); + it.skip('Should the StakingContract record update for new block reward', async () => {}); after(async () => { await network.provider.send('hardhat_setCoinbase', [coinbase.address]); From 655410ce6d8234e5dc8104b31d2aa6ce0eaab07d Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 12 Sep 2022 15:40:07 +0700 Subject: [PATCH 114/190] [IntegrationTest] Fix test description --- test/integration/ActionSubmitReward.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index 283e2a436..e39cd9276 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -199,7 +199,7 @@ describe('[Integration] Submit Block Reward', () => { } }); - it('Should in-jail validator cannot submit block reward', async () => { + it('Should in-jail validator submit block reward', async () => { await network.provider.send('hardhat_setCoinbase', [validator.address]); validatorContract = validatorContract.connect(validator); @@ -214,7 +214,7 @@ describe('[Integration] Submit Block Reward', () => { .withArgs(validator.address, blockRewardAmount); }); - it('Should the StakingContract emit event of recording reward', async () => { + it('Should the StakingContract not emit event of recording reward', async () => { expect(submitRewardTx).not.to.emit(stakingContract, 'PendingPoolUpdated'); }); }); From 983a2293cb09bc4ffacbd158f999018d08e6517e Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 12 Sep 2022 17:47:10 +0700 Subject: [PATCH 115/190] [IntegrationTest] Refactor test --- test/integration/ActionSubmitReward.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index e39cd9276..0a9277ece 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -150,6 +150,10 @@ describe('[Integration] Submit Block Reward', () => { }); }); + after(async () => { + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + }); + it('Should validator can submit block reward', async () => { await network.provider.send('hardhat_setCoinbase', [validator.address]); validatorContract = validatorContract.connect(validator); @@ -172,10 +176,6 @@ describe('[Integration] Submit Block Reward', () => { }); it.skip('Should the StakingContract record update for new block reward', async () => {}); - - after(async () => { - await network.provider.send('hardhat_setCoinbase', [coinbase.address]); - }); }); describe('In-jail validator submits block reward', async () => { From bd2749cc275ab6dc8cd4c296ce93315a8b0aa8b0 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 12 Sep 2022 18:22:49 +0700 Subject: [PATCH 116/190] [Slash] Fix test with assert emitted events --- test/helpers/slash-indicator.ts | 23 +++++++++++++++++++++++ test/slash/SlashIndicator.test.ts | 9 +++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 test/helpers/slash-indicator.ts diff --git a/test/helpers/slash-indicator.ts b/test/helpers/slash-indicator.ts new file mode 100644 index 000000000..f9855f49f --- /dev/null +++ b/test/helpers/slash-indicator.ts @@ -0,0 +1,23 @@ +import { expect } from 'chai'; +import { BigNumberish, ContractTransaction } from 'ethers'; + +import { expectEvent } from '../utils'; +import { ISlashIndicator__factory } from '../types'; + +const contractInterface = ISlashIndicator__factory.createInterface(); + +export const expects = { + emitUnavailabilityIndicatorsResetEvent: async function (tx: ContractTransaction, expectedValidatorList?: string[]) { + await expectEvent( + contractInterface, + 'UnavailabilityIndicatorsReset', + tx, + (event) => { + if (expectedValidatorList) { + expect(event.args[0], 'invalid reset validator list').eql(expectedValidatorList); + } + }, + 1 + ); + }, +}; diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index c7c0261e9..068fab75e 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -13,6 +13,7 @@ import { Address } from 'hardhat-deploy/dist/types'; import { SlashType } from './slashType'; import { Network, slashIndicatorConf } from '../../src/config'; import { BigNumber } from 'ethers'; +import { expects as SlashExpects } from '../../src/script/slash-indicator'; let slashContract: SlashIndicator; @@ -239,7 +240,7 @@ describe('Slash indicator test', () => { await resetCoinbase(); tx = await mockValidatorsContract.resetCounters([coinbases[slasheeIdx].address]); - expect(tx).to.emit(slashContract, 'UnavailabilityIndicatorReset').withArgs(coinbases[slasheeIdx].address); + await SlashExpects.emitUnavailabilityIndicatorsResetEvent(tx, [coinbases[slasheeIdx].address]); await resetLocalCounterForValidatorAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); @@ -267,8 +268,12 @@ describe('Slash indicator test', () => { tx = await mockValidatorsContract.resetCounters(slasheeIdxs.map((_) => coinbases[_].address)); + await SlashExpects.emitUnavailabilityIndicatorsResetEvent( + tx, + slasheeIdxs.map((_) => coinbases[_].address) + ); + for (let j = 0; j < slasheeIdxs.length; j++) { - expect(tx).to.emit(slashContract, 'UnavailabilityIndicatorReset').withArgs(coinbases[slasheeIdxs[j]].address); await resetLocalCounterForValidatorAt(slasheeIdxs[j]); await validateIndicatorAt(slasheeIdxs[j]); } From 6896588f0006f6d5bcfffafe82a8a97debf1cc57 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Mon, 12 Sep 2022 18:23:13 +0700 Subject: [PATCH 117/190] [IntegrationTest] Add WrapUpEpoch test --- .../ronin-validator/RoninValidatorSet.sol | 1 + test/helpers/reward-calculation.ts | 30 +++ test/integration/ActionWrapUpEpoch.test.ts | 236 ++++++++++++++++++ 3 files changed, 267 insertions(+) create mode 100644 test/helpers/reward-calculation.ts diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index afc0f71e6..4d26ac8db 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -170,6 +170,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { } if (_periodEnding) { + // TODO: reset for candidates / kicked validators ISlashIndicator(_slashIndicatorContract).resetCounters(_validators); } diff --git a/test/helpers/reward-calculation.ts b/test/helpers/reward-calculation.ts new file mode 100644 index 000000000..d5e0ee22a --- /dev/null +++ b/test/helpers/reward-calculation.ts @@ -0,0 +1,30 @@ +import { expect } from 'chai'; +import { BigNumberish, ContractTransaction } from 'ethers'; + +import { expectEvent } from '../utils'; +import { RewardCalculation__factory } from '../types'; + +const contractInterface = RewardCalculation__factory.createInterface(); + +export const expects = { + emitSettledPoolsUpdatedEvent: async function ( + tx: ContractTransaction, + expectedPoolList?: string[], + expectedAccumulatedRpsList?: BigNumberish[] + ) { + await expectEvent( + contractInterface, + 'SettledPoolsUpdated', + tx, + (event) => { + if (expectedPoolList) { + expect(event.args[0], 'invalid pool list').eql(expectedPoolList); + } + if (expectedAccumulatedRpsList) { + expect(event.args[1], 'invalid accumulated rps list').eql(expectedAccumulatedRpsList); + } + }, + 1 + ); + }, +}; diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index e69de29bb..f17d02e2e 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -0,0 +1,236 @@ +import { expect } from 'chai'; +import { network, ethers } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; + +import { + SlashIndicator, + SlashIndicator__factory, + Staking, + Staking__factory, + MockRoninValidatorSetEpochSetterAndQueryInfo__factory, + MockRoninValidatorSetEpochSetterAndQueryInfo, + TransparentUpgradeableProxy__factory, +} from '../../src/types'; +import { Network, slashIndicatorConf, roninValidatorSetConf, stakingConfig, initAddress } from '../../src/config'; +import { BigNumber, ContractTransaction } from 'ethers'; +import { expects as StakingExpects } from '../../src/script/reward-calculation'; +import { expects as SlashExpects } from '../../src/script/slash-indicator'; +import { mineBatchTxs } from '../utils'; + +let slashContract: SlashIndicator; +let stakingContract: Staking; +let validatorContract: MockRoninValidatorSetEpochSetterAndQueryInfo; + +let coinbase: SignerWithAddress; +let deployer: SignerWithAddress; +let governanceAdmin: SignerWithAddress; +let proxyAdmin: SignerWithAddress; +let validatorCandidates: SignerWithAddress[]; + +const slashFelonyAmount = BigNumber.from(1); +const slashDoubleSignAmount = 1000; +const maxValidatorNumber = 4; +const minValidatorBalance = BigNumber.from(100); +const felonyJailDuration = 28800 * 2; +const misdemeanorThreshold = 10; +const felonyThreshold = 20; +const blockRewardAmount = BigNumber.from(2); + +describe('[Integration] Wrap up epoch', () => { + before(async () => { + [coinbase, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + validatorCandidates = validatorCandidates.slice(0, 5); + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + + if (network.name == Network.Hardhat) { + initAddress[network.name] = { + governanceAdmin: governanceAdmin.address, + }; + slashIndicatorConf[network.name] = { + misdemeanorThreshold: misdemeanorThreshold, + felonyThreshold: felonyThreshold, + slashFelonyAmount: slashFelonyAmount, + slashDoubleSignAmount: slashDoubleSignAmount, + felonyJailBlocks: felonyJailDuration, + }; + roninValidatorSetConf[network.name] = { + maxValidatorNumber: 21, + numberOfBlocksInEpoch: 600, + numberOfEpochsInPeriod: 48, // 1 day + }; + stakingConfig[network.name] = { + minValidatorBalance: minValidatorBalance, + maxValidatorCandidate: maxValidatorNumber, + }; + } + + const nonce = await deployer.getTransactionCount(); + const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); + const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); + + const slashLogicContract = await new SlashIndicator__factory(deployer).deploy(); + await slashLogicContract.deployed(); + + const slashProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + slashLogicContract.address, + proxyAdmin.address, + slashLogicContract.interface.encodeFunctionData('initialize', [ + slashIndicatorConf[network.name]!.misdemeanorThreshold, + slashIndicatorConf[network.name]!.felonyThreshold, + roninValidatorSetAddr, + slashIndicatorConf[network.name]!.slashFelonyAmount, + slashIndicatorConf[network.name]!.slashDoubleSignAmount, + slashIndicatorConf[network.name]!.felonyJailBlocks, + ]) + ); + await slashProxyContract.deployed(); + slashContract = SlashIndicator__factory.connect(slashProxyContract.address, deployer); + + validatorContract = await new MockRoninValidatorSetEpochSetterAndQueryInfo__factory(deployer).deploy( + governanceAdmin.address, + slashContract.address, + stakingContractAddr, + maxValidatorNumber + ); + await validatorContract.deployed(); + + const stakingLogicContract = await new Staking__factory(deployer).deploy(); + await stakingLogicContract.deployed(); + + const stakingProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + stakingLogicContract.address, + proxyAdmin.address, + stakingLogicContract.interface.encodeFunctionData('initialize', [ + validatorContract.address, + governanceAdmin.address, + 100, + minValidatorBalance, + ]) + ); + await stakingProxyContract.deployed(); + stakingContract = Staking__factory.connect(stakingProxyContract.address, deployer); + + expect(roninValidatorSetAddr.toLowerCase(), 'validator set contract mismatch').eq( + validatorContract.address.toLowerCase() + ); + expect(stakingContractAddr.toLowerCase(), 'staking contract mismatch').eq(stakingContract.address.toLowerCase()); + }); + + describe('Configuration test', () => { + describe('ValidatorSetContract configuration', async () => { + it('Should the ValidatorSetContract config the StakingContract correctly', async () => { + let _stakingContract = await validatorContract.stakingContract(); + expect(_stakingContract).to.eq(stakingContract.address); + }); + + it('Should the ValidatorSetContract config the Slashing correctly', async () => { + let _slashingContract = await validatorContract.slashIndicatorContract(); + expect(_slashingContract).to.eq(slashContract.address); + }); + }); + + describe('StakingContract configuration', async () => { + it('Should the StakingContract config the ValidatorSetContract correctly', async () => { + let _validatorSetContract = await stakingContract.validatorContract(); + expect(_validatorSetContract).to.eq(validatorContract.address); + }); + }); + + describe('SlashIndicatorContract configuration', async () => { + it('Should the SlashIndicatorContract config the ValidatorSetContract correctly', async () => { + let _validatorSetContract = await slashContract.validatorContract(); + expect(_validatorSetContract).to.eq(validatorContract.address); + }); + }); + }); + + describe('Flow test', async () => { + let wrapUpTx: ContractTransaction; + let validators: SignerWithAddress[]; + + before(async () => { + validators = validatorCandidates.slice(0, 4); + + for (let i = 0; i < validators.length; i++) { + await stakingContract + .connect(validatorCandidates[i]) + .proposeValidator(validatorCandidates[i].address, validatorCandidates[i].address, 2_00, { + value: minValidatorBalance.mul(2).add(i), + }); + } + + await mineBatchTxs(async () => { + await validatorContract.connect(coinbase).endEpoch(); + await validatorContract.connect(coinbase).wrapUpEpoch(); + }); + + await network.provider.send('hardhat_setCoinbase', [validators[0].address]); + validatorContract = validatorContract.connect(validators[0]); + await validatorContract.submitBlockReward({ + value: blockRewardAmount, + }); + }); + + describe('Wrap up epoch: at the end of the epoch', async () => { + it('Should validator not be able to wrap up the epoch twice, in the same epoch', async () => { + await mineBatchTxs(async () => { + await validatorContract.endEpoch(); + await validatorContract.wrapUpEpoch(); + let duplicatedWrapUpTx = validatorContract.wrapUpEpoch(); + + await expect(duplicatedWrapUpTx).to.be.revertedWith('RoninValidatorSet: query for already wrapped up epoch'); + }); + }); + + it('Should validator be able to wrap up the epoch', async () => { + await mineBatchTxs(async () => { + await validatorContract.endEpoch(); + wrapUpTx = await validatorContract.wrapUpEpoch(); + }); + }); + + describe('ValidatorSetContract internal actions', async () => { + it.skip('Should the mining award distributed', async () => { + // TODO: should this be in the unit test? + }); + + it.skip('Should the validator set get updated', async () => { + // TODO: should this be in the unit test? + }); + + it.skip('Should the treasury address of the validator received mining reward', async () => { + // TODO: should this be in the unit test? + }); + }); + + describe('StakingContract internal actions: settle reward pool', async () => { + it.skip('Should the StakingContract update rewarding info for validators and delegators', async () => { + // TODO: should this be in the unit test? + }); + + it('Should the StakingContract emit event of settling reward', async () => { + await StakingExpects.emitSettledPoolsUpdatedEvent(wrapUpTx, validators.map((_) => _.address).reverse()); + }); + }); + }); + + describe('Wrap up epoch: at the end of the period', async () => { + it('Should the ValidatorSet not reset counter, when the period is not ended', async () => { + await mineBatchTxs(async () => { + await validatorContract.endEpoch(); + wrapUpTx = await validatorContract.wrapUpEpoch(); + }); + await expect(wrapUpTx).not.to.emit(slashContract, 'UnavailabilityIndicatorsReset'); + }); + + it('Should the ValidatorSet reset counter in SlashIndicator contract', async () => { + await mineBatchTxs(async () => { + await validatorContract.endEpoch(); + await validatorContract.endPeriod(); + wrapUpTx = await validatorContract.wrapUpEpoch(); + }); + await SlashExpects.emitUnavailabilityIndicatorsResetEvent(wrapUpTx, validators.map((_) => _.address).reverse()); + }); + }); + }); +}); From 2f1c33d5a7d22b23211bffa8324ea12f8073956b Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 13 Sep 2022 11:42:02 +0700 Subject: [PATCH 118/190] [IStaking] Fix typo --- contracts/interfaces/IStaking.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 9ba25a5c1..010e05e57 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -67,7 +67,7 @@ interface IStaking is IRewardPool { function governanceAdmin() external view returns (address); /** - * @dev Returns validator contracts + * @dev Returns validator contract */ function validatorContract() external view returns (address); From 244855aee8ec0993ec652b2a626b78a602b1b813 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 13 Sep 2022 11:42:48 +0700 Subject: [PATCH 119/190] [Slash] Emit events for set functions --- contracts/interfaces/ISlashIndicator.sol | 20 ++++++++++++++++++++ contracts/slashing/SlashIndicator.sol | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index d26fd94cf..c79570b3d 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -9,6 +9,14 @@ interface ISlashIndicator { event ValidatorSlashed(address indexed validator, SlashType slashType); /// @dev Emitted when the validator indicators are reset event UnavailabilityIndicatorsReset(address[] validators); + /// @dev Emitted when the thresholds updated + event SlashThresholdsUpdated(uint256 felonyThreshold, uint256 misdemeanorThreshold); + /// @dev Emitted when the amount of slashing felony updated + event SlashFelonyAmountUpdated(uint256 slashFelonyAmount); + /// @dev Emitted when the amount of slashing double sign updated + event SlashDoubleSignAmountUpdated(uint256 slashDoubleSignAmount); + /// @dev Emiited when the duration of jailing felony updated + event FelonyJailDurationUpdated(uint256 felonyJailDuration); enum SlashType { UNKNOWN, @@ -59,6 +67,9 @@ interface ISlashIndicator { * * Requirements: * - Only governance admin can call this method + * + * Emits the event `SlashThresholdsUpdated`. + * */ function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external; @@ -67,6 +78,9 @@ interface ISlashIndicator { * * Requirements: * - Only governance admin can call this method + * + * Emits the event `SlashFelonyAmountUpdated`. + * */ function setSlashFelonyAmount(uint256 _slashFelonyAmount) external; @@ -75,6 +89,9 @@ interface ISlashIndicator { * * Requirements: * - Only governance admin can call this method + * + * Emits the event `SlashDoubleSignAmountUpdated`. + * */ function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external; @@ -83,6 +100,9 @@ interface ISlashIndicator { * * Requirements: * - Only governance admin can call this method + * + * Emits the event `FelonyJailDurationUpdated`. + * */ function setFelonyJailDuration(uint256 _felonyJailDuration) external; diff --git a/contracts/slashing/SlashIndicator.sol b/contracts/slashing/SlashIndicator.sol index bd2702f6e..3e8503a5b 100644 --- a/contracts/slashing/SlashIndicator.sol +++ b/contracts/slashing/SlashIndicator.sol @@ -148,6 +148,7 @@ contract SlashIndicator is ISlashIndicator, Initializable { { felonyThreshold = _felonyThreshold; misdemeanorThreshold = _misdemeanorThreshold; + emit SlashThresholdsUpdated(_felonyThreshold, _misdemeanorThreshold); } /** @@ -155,6 +156,7 @@ contract SlashIndicator is ISlashIndicator, Initializable { */ function setSlashFelonyAmount(uint256 _slashFelonyAmount) external override onlyGovernanceAdmin { slashFelonyAmount = _slashFelonyAmount; + emit SlashFelonyAmountUpdated(_slashFelonyAmount); } /** @@ -162,6 +164,7 @@ contract SlashIndicator is ISlashIndicator, Initializable { */ function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external override onlyGovernanceAdmin { slashDoubleSignAmount = _slashDoubleSignAmount; + emit SlashDoubleSignAmountUpdated(_slashDoubleSignAmount); } /** @@ -169,6 +172,7 @@ contract SlashIndicator is ISlashIndicator, Initializable { */ function setFelonyJailDuration(uint256 _felonyJailDuration) external override onlyGovernanceAdmin { felonyJailDuration = _felonyJailDuration; + emit FelonyJailDurationUpdated(_felonyJailDuration); } /////////////////////////////////////////////////////////////////////////////////////// From 879515b746b0dd491be04eb73ca6aac975f08950 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 13 Sep 2022 14:35:32 +0700 Subject: [PATCH 120/190] [IntegrationTest] Add more test for wrapUp function --- test/integration/ActionWrapUpEpoch.test.ts | 97 ++++++++++++++++++++-- 1 file changed, 89 insertions(+), 8 deletions(-) diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index f17d02e2e..4b21934e3 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -15,6 +15,7 @@ import { Network, slashIndicatorConf, roninValidatorSetConf, stakingConfig, init import { BigNumber, ContractTransaction } from 'ethers'; import { expects as StakingExpects } from '../../src/script/reward-calculation'; import { expects as SlashExpects } from '../../src/script/slash-indicator'; +import { expects as ValidatorSetExpects } from '../../src/script/ronin-validator-set'; import { mineBatchTxs } from '../utils'; let slashContract: SlashIndicator; @@ -29,7 +30,7 @@ let validatorCandidates: SignerWithAddress[]; const slashFelonyAmount = BigNumber.from(1); const slashDoubleSignAmount = 1000; -const maxValidatorNumber = 4; +const maxValidatorNumber = 3; const minValidatorBalance = BigNumber.from(100); const felonyJailDuration = 28800 * 2; const misdemeanorThreshold = 10; @@ -39,7 +40,6 @@ const blockRewardAmount = BigNumber.from(2); describe('[Integration] Wrap up epoch', () => { before(async () => { [coinbase, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); - validatorCandidates = validatorCandidates.slice(0, 5); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); if (network.name == Network.Hardhat) { @@ -54,7 +54,7 @@ describe('[Integration] Wrap up epoch', () => { felonyJailBlocks: felonyJailDuration, }; roninValidatorSetConf[network.name] = { - maxValidatorNumber: 21, + maxValidatorNumber: maxValidatorNumber, numberOfBlocksInEpoch: 600, numberOfEpochsInPeriod: 48, // 1 day }; @@ -144,7 +144,7 @@ describe('[Integration] Wrap up epoch', () => { }); }); - describe('Flow test', async () => { + describe('Flow test on one validator', async () => { let wrapUpTx: ContractTransaction; let validators: SignerWithAddress[]; @@ -164,13 +164,17 @@ describe('[Integration] Wrap up epoch', () => { await validatorContract.connect(coinbase).wrapUpEpoch(); }); - await network.provider.send('hardhat_setCoinbase', [validators[0].address]); - validatorContract = validatorContract.connect(validators[0]); + await network.provider.send('hardhat_setCoinbase', [validators[3].address]); + validatorContract = validatorContract.connect(validators[3]); await validatorContract.submitBlockReward({ value: blockRewardAmount, }); }); + after(async () => { + coinbase = validators[3]; + }); + describe('Wrap up epoch: at the end of the epoch', async () => { it('Should validator not be able to wrap up the epoch twice, in the same epoch', async () => { await mineBatchTxs(async () => { @@ -209,7 +213,13 @@ describe('[Integration] Wrap up epoch', () => { }); it('Should the StakingContract emit event of settling reward', async () => { - await StakingExpects.emitSettledPoolsUpdatedEvent(wrapUpTx, validators.map((_) => _.address).reverse()); + await StakingExpects.emitSettledPoolsUpdatedEvent( + wrapUpTx, + validators + .slice(1, 4) + .map((_) => _.address) + .reverse() + ); }); }); }); @@ -229,7 +239,78 @@ describe('[Integration] Wrap up epoch', () => { await validatorContract.endPeriod(); wrapUpTx = await validatorContract.wrapUpEpoch(); }); - await SlashExpects.emitUnavailabilityIndicatorsResetEvent(wrapUpTx, validators.map((_) => _.address).reverse()); + await SlashExpects.emitUnavailabilityIndicatorsResetEvent( + wrapUpTx, + validators + .slice(1, 4) + .map((_) => _.address) + .reverse() + ); + }); + }); + }); + + describe('Flow test on many validators', async () => { + let wrapUpTx: ContractTransaction; + let validators: SignerWithAddress[]; + + before(async () => { + validators = validatorCandidates.slice(4, 8); + + for (let i = 0; i < validators.length; i++) { + await stakingContract + .connect(validators[i]) + .proposeValidator(validators[i].address, validators[i].address, 2_00, { + value: minValidatorBalance.mul(3).add(i), + }); + } + + await mineBatchTxs(async () => { + await validatorContract.connect(coinbase).endEpoch(); + await validatorContract.connect(coinbase).wrapUpEpoch(); + }); + + await network.provider.send('hardhat_setCoinbase', [validators[3].address]); + validatorContract = validatorContract.connect(validators[3]); + await validatorContract.submitBlockReward({ + value: blockRewardAmount, + }); + }); + + describe('One validator get slashed between period', async () => { + before(async () => { + await mineBatchTxs(async () => { + await validatorContract.endEpoch(); + await validatorContract.wrapUpEpoch(); + }); + + for (let i = 0; i < felonyThreshold; i++) { + await slashContract.connect(validators[3]).slash(validators[1].address); + } + }); + + it('Should the validator set get updated (excluding the slashed validator)', async () => { + await mineBatchTxs(async () => { + await validatorContract.endEpoch(); + wrapUpTx = await validatorContract.wrapUpEpoch(); + }); + + await ValidatorSetExpects.emitValidatorSetUpdatedEvent( + wrapUpTx, + [validators[0], validators[2], validators[3]].map((_) => _.address).reverse() + ); + }); + + it('Should the validators in the previous epoch (including slashed one) got slashing counter reset, when the epoch ends', async () => { + await mineBatchTxs(async () => { + await validatorContract.endEpoch(); + await validatorContract.endPeriod(); + wrapUpTx = await validatorContract.wrapUpEpoch(); + }); + await SlashExpects.emitUnavailabilityIndicatorsResetEvent( + wrapUpTx, + [validators[0], validators[2], validators[3]].map((_) => _.address).reverse() + ); }); }); }); From c9b46aa2e189fdff8cd91054ccee8d86f9bb321a Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 13 Sep 2022 14:45:13 +0700 Subject: [PATCH 121/190] [IntegrationTest] Remove unnecessary test --- test/integration/ActionWrapUpEpoch.test.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index 4b21934e3..8dfcbb27a 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -193,25 +193,9 @@ describe('[Integration] Wrap up epoch', () => { }); }); - describe('ValidatorSetContract internal actions', async () => { - it.skip('Should the mining award distributed', async () => { - // TODO: should this be in the unit test? - }); - - it.skip('Should the validator set get updated', async () => { - // TODO: should this be in the unit test? - }); - - it.skip('Should the treasury address of the validator received mining reward', async () => { - // TODO: should this be in the unit test? - }); - }); + describe.skip('ValidatorSetContract internal actions', async () => {}); describe('StakingContract internal actions: settle reward pool', async () => { - it.skip('Should the StakingContract update rewarding info for validators and delegators', async () => { - // TODO: should this be in the unit test? - }); - it('Should the StakingContract emit event of settling reward', async () => { await StakingExpects.emitSettledPoolsUpdatedEvent( wrapUpTx, From d77ce8a03b9a0c6d361afcce889a9787eaf98b26 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 13 Sep 2022 15:42:05 +0700 Subject: [PATCH 122/190] [RoninValidatorSet] Add setter for unchangeable variables --- contracts/interfaces/IRoninValidatorSet.sol | 56 +++++++++++++++++++ contracts/mocks/MockValidatorSet.sol | 8 +++ .../ronin-validator/RoninValidatorSet.sol | 47 +++++++++++++++- 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index 5b7fea33f..8536c139e 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -17,6 +17,14 @@ interface IRoninValidatorSet { event StakingRewardDistributed(uint256 amount); /// @dev Emitted when the validator set is updated event ValidatorSetUpdated(address[]); + /// @dev Emitted when the governance admin is updated + event GovernanceAdminUpdated(address); + /// @dev Emitted when the number of max validator is updated + event MaxValidatorNumberUpdated(uint256); + /// @dev Emitted when the number of blocks in epoch is updated + event NumberOfBlocksInEpochUpdated(uint256); + /// @dev Emitted when the number of epochs in period is updated + event NumberOfEpochsInPeriodUpdated(uint256); /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR COINBASE // @@ -141,4 +149,52 @@ interface IRoninValidatorSet { * @dev Returns whether the period ending is at the block number `_block`. */ function periodEndingAt(uint256 _block) external view returns (bool); + + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR GOVERNANCE ADMIN // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @dev Updates the governance admin + * + * Requirements: + * - The method caller is the governance admin + * + * Emits the event `GovernanceAdminUpdated` + * + */ + function setGovernanceAdmin(address _governanceAdmin) external; + + /** + * @dev Updates the max validator number + * + * Requirements: + * - The method caller is the governance admin + * + * Emits the event `MaxValidatorNumberUpdated` + * + */ + function setMaxValidatorNumber(uint256 _maxValidatorNumber) external; + + /** + * @dev Updates the number of blocks in epoch + * + * Requirements: + * - The method caller is the governance admin + * + * Emits the event `NumberOfBlocksInEpochUpdated` + * + */ + function setNumberOfBlocksInEpoch(uint256 _numberOfBlocksInEpoch) external; + + /** + * @dev Updates the number of epochs in period + * + * Requirements: + * - The method caller is the governance admin + * + * Emits the event `NumberOfEpochsInPeriodUpdated` + * + */ + function setNumberOfEpochsInPeriod(uint256 _numberOfEpochsInPeriod) external; } diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index a89193fce..2920cb7df 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -93,4 +93,12 @@ contract MockValidatorSet is IRoninValidatorSet { function resetCounters(address[] calldata _validatorAddrs) external { ISlashIndicator(slashIndicatorContract).resetCounters(_validatorAddrs); } + + function setGovernanceAdmin(address _governanceAdmin) external override {} + + function setMaxValidatorNumber(uint256 _maxValidatorNumber) external override {} + + function setNumberOfBlocksInEpoch(uint256 _numberOfBlocksInEpoch) external override {} + + function setNumberOfEpochsInPeriod(uint256 _numberOfEpochsInPeriod) external override {} } diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index 4d26ac8db..0a7f6ea3b 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -13,7 +13,7 @@ import "../libraries/Math.sol"; contract RoninValidatorSet is IRoninValidatorSet, Initializable { /// @dev Governance admin address. - address internal _governanceAdmin; // TODO(Thor): add setter. + address internal _governanceAdmin; /// @dev Slash indicator contract address. address internal _slashIndicatorContract; // Change type to address for testing purpose /// @dev Staking contract address. @@ -59,6 +59,11 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _; } + modifier onlyGovernanceAdmin() { + require(msg.sender == _governanceAdmin, "RoninValidatorSet: method caller must be governance admin"); + _; + } + modifier whenEpochEnding() { require(epochEndingAt(block.number), "RoninValidatorSet: only allowed at the end of epoch"); _; @@ -327,6 +332,46 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { return _numberOfBlocksInEpoch; } + /////////////////////////////////////////////////////////////////////////////////////// + // FUNCTIONS FOR GOVERNANCE ADMIN // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @inheritdoc IRoninValidatorSet + */ + function setGovernanceAdmin(address __governanceAdmin) external override onlyGovernanceAdmin { + if (__governanceAdmin == address(0) || __governanceAdmin == _governanceAdmin) { + return; + } + + _governanceAdmin == __governanceAdmin; + emit GovernanceAdminUpdated(__governanceAdmin); + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function setMaxValidatorNumber(uint256 __maxValidatorNumber) external override onlyGovernanceAdmin { + _maxValidatorNumber = __maxValidatorNumber; + emit MaxValidatorNumberUpdated(__maxValidatorNumber); + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function setNumberOfBlocksInEpoch(uint256 __numberOfBlocksInEpoch) external override onlyGovernanceAdmin { + _numberOfBlocksInEpoch = __numberOfBlocksInEpoch; + emit NumberOfBlocksInEpochUpdated(__numberOfBlocksInEpoch); + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function setNumberOfEpochsInPeriod(uint256 __numberOfEpochsInPeriod) external override onlyGovernanceAdmin { + _numberOfEpochsInPeriod == __numberOfEpochsInPeriod; + emit NumberOfEpochsInPeriodUpdated(__numberOfEpochsInPeriod); + } + /////////////////////////////////////////////////////////////////////////////////////// // HELPER FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// From cecf90c2ca3fe8650131c7a97b4094fa40a112ea Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 13 Sep 2022 16:46:09 +0700 Subject: [PATCH 123/190] All: resolve conflicts --- ...l => MockRoninValidatorSetEpochSetter.sol} | 2 +- test/helpers/reward-calculation.ts | 4 +-- test/helpers/slash-indicator.ts | 4 +-- .../integration/ActionSlashValidators.test.ts | 23 +++++++++++----- test/integration/ActionSubmitReward.test.ts | 21 ++++++++++----- test/integration/ActionWrapUpEpoch.test.ts | 27 ++++++++++++------- test/integration/Configuration.test.ts | 16 +++++++++-- test/slash/SlashIndicator.test.ts | 2 +- test/validator/RoninValidatorSet.test.ts | 10 +++---- 9 files changed, 74 insertions(+), 35 deletions(-) rename contracts/mocks/{MockRoninValidatorSetEpochSetterAndQueryInfo.sol => MockRoninValidatorSetEpochSetter.sol} (96%) diff --git a/contracts/mocks/MockRoninValidatorSetEpochSetterAndQueryInfo.sol b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol similarity index 96% rename from contracts/mocks/MockRoninValidatorSetEpochSetterAndQueryInfo.sol rename to contracts/mocks/MockRoninValidatorSetEpochSetter.sol index 258305ba8..91a246c8c 100644 --- a/contracts/mocks/MockRoninValidatorSetEpochSetterAndQueryInfo.sol +++ b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.9; import "../ronin-validator/RoninValidatorSet.sol"; -contract MockRoninValidatorSetEpochSetterAndQueryInfo is RoninValidatorSet { +contract MockRoninValidatorSetEpochSetter is RoninValidatorSet { uint256[] internal _epochs; uint256[] internal _periods; diff --git a/test/helpers/reward-calculation.ts b/test/helpers/reward-calculation.ts index d5e0ee22a..293cd3e1d 100644 --- a/test/helpers/reward-calculation.ts +++ b/test/helpers/reward-calculation.ts @@ -1,8 +1,8 @@ import { expect } from 'chai'; import { BigNumberish, ContractTransaction } from 'ethers'; -import { expectEvent } from '../utils'; -import { RewardCalculation__factory } from '../types'; +import { expectEvent } from './utils'; +import { RewardCalculation__factory } from '../../src/types'; const contractInterface = RewardCalculation__factory.createInterface(); diff --git a/test/helpers/slash-indicator.ts b/test/helpers/slash-indicator.ts index f9855f49f..d8a58f336 100644 --- a/test/helpers/slash-indicator.ts +++ b/test/helpers/slash-indicator.ts @@ -1,8 +1,8 @@ import { expect } from 'chai'; import { BigNumberish, ContractTransaction } from 'ethers'; -import { expectEvent } from '../utils'; -import { ISlashIndicator__factory } from '../types'; +import { expectEvent } from './utils'; +import { ISlashIndicator__factory } from '../../src/types'; const contractInterface = ISlashIndicator__factory.createInterface(); diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index 3a96e6c33..938b0e3ed 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -7,20 +7,21 @@ import { SlashIndicator__factory, Staking, Staking__factory, - MockRoninValidatorSetEpochSetterAndQueryInfo__factory, - MockRoninValidatorSetEpochSetterAndQueryInfo, + MockRoninValidatorSetEpochSetter__factory, + MockRoninValidatorSetEpochSetter, TransparentUpgradeableProxy__factory, + StakingVesting__factory, } from '../../src/types'; import { Network, slashIndicatorConf, roninValidatorSetConf, stakingConfig, initAddress } from '../../src/config'; import { BigNumber, ContractTransaction } from 'ethers'; import { SlashType } from '../slash/slashType'; -import { expects as RoninValidatorSetExpects } from '../../src/script/ronin-validator-set'; +import { expects as RoninValidatorSetExpects } from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../utils'; import { Address } from 'hardhat-deploy/dist/types'; let slashContract: SlashIndicator; let stakingContract: Staking; -let validatorContract: MockRoninValidatorSetEpochSetterAndQueryInfo; +let validatorContract: MockRoninValidatorSetEpochSetter; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; @@ -65,8 +66,8 @@ describe('[Integration] Slash validators', () => { } const nonce = await deployer.getTransactionCount(); - const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); - const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); + const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); + const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 6 }); const slashLogicContract = await new SlashIndicator__factory(deployer).deploy(); await slashLogicContract.deployed(); @@ -86,10 +87,18 @@ describe('[Integration] Slash validators', () => { await slashProxyContract.deployed(); slashContract = SlashIndicator__factory.connect(slashProxyContract.address, deployer); - validatorContract = await new MockRoninValidatorSetEpochSetterAndQueryInfo__factory(deployer).deploy( + const stakingVestingLogic = await new StakingVesting__factory(deployer).deploy(); + const stakingVesting = await new TransparentUpgradeableProxy__factory(deployer).deploy( + stakingVestingLogic.address, + proxyAdmin.address, + stakingVestingLogic.interface.encodeFunctionData('initialize', [0, roninValidatorSetAddr]) + ); + + validatorContract = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy( governanceAdmin.address, slashContract.address, stakingContractAddr, + stakingVesting.address, maxValidatorNumber ); await validatorContract.deployed(); diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index 0a9277ece..f0ea27f2e 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -8,9 +8,10 @@ import { SlashIndicator__factory, Staking, Staking__factory, - MockRoninValidatorSetEpochSetterAndQueryInfo__factory, - MockRoninValidatorSetEpochSetterAndQueryInfo, + MockRoninValidatorSetEpochSetter__factory, + MockRoninValidatorSetEpochSetter, TransparentUpgradeableProxy__factory, + StakingVesting__factory, } from '../../src/types'; import { Network, slashIndicatorConf, roninValidatorSetConf, stakingConfig, initAddress } from '../../src/config'; import { BigNumber, ContractTransaction } from 'ethers'; @@ -18,7 +19,7 @@ import { mineBatchTxs } from '../utils'; let slashContract: SlashIndicator; let stakingContract: Staking; -let validatorContract: MockRoninValidatorSetEpochSetterAndQueryInfo; +let validatorContract: MockRoninValidatorSetEpochSetter; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; @@ -64,8 +65,8 @@ describe('[Integration] Submit Block Reward', () => { } const nonce = await deployer.getTransactionCount(); - const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); - const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); + const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); + const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 6 }); const slashLogicContract = await new SlashIndicator__factory(deployer).deploy(); await slashLogicContract.deployed(); @@ -85,10 +86,18 @@ describe('[Integration] Submit Block Reward', () => { await slashProxyContract.deployed(); slashContract = SlashIndicator__factory.connect(slashProxyContract.address, deployer); - validatorContract = await new MockRoninValidatorSetEpochSetterAndQueryInfo__factory(deployer).deploy( + const stakingVestingLogic = await new StakingVesting__factory(deployer).deploy(); + const stakingVesting = await new TransparentUpgradeableProxy__factory(deployer).deploy( + stakingVestingLogic.address, + proxyAdmin.address, + stakingVestingLogic.interface.encodeFunctionData('initialize', [0, roninValidatorSetAddr]) + ); + + validatorContract = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy( governanceAdmin.address, slashContract.address, stakingContractAddr, + stakingVesting.address, maxValidatorNumber ); await validatorContract.deployed(); diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index 8dfcbb27a..68921c4f5 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -7,20 +7,21 @@ import { SlashIndicator__factory, Staking, Staking__factory, - MockRoninValidatorSetEpochSetterAndQueryInfo__factory, - MockRoninValidatorSetEpochSetterAndQueryInfo, + MockRoninValidatorSetEpochSetter__factory, + MockRoninValidatorSetEpochSetter, TransparentUpgradeableProxy__factory, + StakingVesting__factory, } from '../../src/types'; import { Network, slashIndicatorConf, roninValidatorSetConf, stakingConfig, initAddress } from '../../src/config'; import { BigNumber, ContractTransaction } from 'ethers'; -import { expects as StakingExpects } from '../../src/script/reward-calculation'; -import { expects as SlashExpects } from '../../src/script/slash-indicator'; -import { expects as ValidatorSetExpects } from '../../src/script/ronin-validator-set'; +import { expects as StakingExpects } from '../helpers/reward-calculation'; +import { expects as SlashExpects } from '../helpers/slash-indicator'; +import { expects as ValidatorSetExpects } from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../utils'; let slashContract: SlashIndicator; let stakingContract: Staking; -let validatorContract: MockRoninValidatorSetEpochSetterAndQueryInfo; +let validatorContract: MockRoninValidatorSetEpochSetter; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; @@ -65,8 +66,8 @@ describe('[Integration] Wrap up epoch', () => { } const nonce = await deployer.getTransactionCount(); - const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 2 }); - const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); + const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); + const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 6 }); const slashLogicContract = await new SlashIndicator__factory(deployer).deploy(); await slashLogicContract.deployed(); @@ -86,10 +87,18 @@ describe('[Integration] Wrap up epoch', () => { await slashProxyContract.deployed(); slashContract = SlashIndicator__factory.connect(slashProxyContract.address, deployer); - validatorContract = await new MockRoninValidatorSetEpochSetterAndQueryInfo__factory(deployer).deploy( + const stakingVestingLogic = await new StakingVesting__factory(deployer).deploy(); + const stakingVesting = await new TransparentUpgradeableProxy__factory(deployer).deploy( + stakingVestingLogic.address, + proxyAdmin.address, + stakingVestingLogic.interface.encodeFunctionData('initialize', [0, roninValidatorSetAddr]) + ); + + validatorContract = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy( governanceAdmin.address, slashContract.address, stakingContractAddr, + stakingVesting.address, maxValidatorNumber ); await validatorContract.deployed(); diff --git a/test/integration/Configuration.test.ts b/test/integration/Configuration.test.ts index 713e17d11..24cccc9c5 100644 --- a/test/integration/Configuration.test.ts +++ b/test/integration/Configuration.test.ts @@ -9,9 +9,15 @@ import { Staking__factory, RoninValidatorSet, RoninValidatorSet__factory, - TransparentUpgradeableProxy__factory, } from '../../src/types'; -import { Network, slashIndicatorConf, roninValidatorSetConf, stakingConfig, initAddress } from '../../src/config'; +import { + Network, + slashIndicatorConf, + roninValidatorSetConf, + stakingConfig, + stakingVestingConfig, + initAddress, +} from '../../src/config'; import { BigNumber } from 'ethers'; let slashContract: SlashIndicator; @@ -31,6 +37,8 @@ const minValidatorBalance = BigNumber.from(100); const felonyJailDuration = 28800 * 2; const misdemeanorThreshold = 10; const felonyThreshold = 20; +const bonusPerBlock = BigNumber.from(1); +const topUpAmount = BigNumber.from(10000); describe('[Integration] Configuration check', () => { before(async () => { @@ -56,6 +64,10 @@ describe('[Integration] Configuration check', () => { minValidatorBalance: minValidatorBalance, maxValidatorCandidate: maxValidatorNumber, }; + stakingVestingConfig[network.name] = { + bonusPerBlock: bonusPerBlock, + topupAmount: topUpAmount, + }; } await deployments.fixture(['CalculateAddresses', 'RoninValidatorSetProxy', 'SlashIndicatorProxy', 'StakingProxy']); diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index 068fab75e..55b0531ce 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -13,7 +13,7 @@ import { Address } from 'hardhat-deploy/dist/types'; import { SlashType } from './slashType'; import { Network, slashIndicatorConf } from '../../src/config'; import { BigNumber } from 'ethers'; -import { expects as SlashExpects } from '../../src/script/slash-indicator'; +import { expects as SlashExpects } from '../helpers/slash-indicator'; let slashContract: SlashIndicator; diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index 4b34fb7c4..01736dfc9 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -5,18 +5,18 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { Staking, - MockRoninValidatorSetEpochSetterAndQueryInfo, - MockRoninValidatorSetEpochSetterAndQueryInfo__factory, + MockRoninValidatorSetEpochSetter, + MockRoninValidatorSetEpochSetter__factory, Staking__factory, TransparentUpgradeableProxy__factory, MockSlashIndicator, MockSlashIndicator__factory, StakingVesting__factory, } from '../../src/types'; -import * as RoninValidatorSet from '../../src/script/ronin-validator-set'; +import * as RoninValidatorSet from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../utils'; -let roninValidatorSet: MockRoninValidatorSetEpochSetterAndQueryInfo; +let roninValidatorSet: MockRoninValidatorSetEpochSetter; let stakingContract: Staking; let slashIndicator: MockSlashIndicator; @@ -58,7 +58,7 @@ describe('Ronin Validator Set test', () => { ); await slashIndicator.deployed(); - roninValidatorSet = await new MockRoninValidatorSetEpochSetterAndQueryInfo__factory(deployer).deploy( + roninValidatorSet = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy( governanceAdmin.address, slashIndicator.address, stakingContractAddr, From f632aa21c254f445c5b06b61586470634cf655b3 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 13 Sep 2022 16:57:07 +0700 Subject: [PATCH 124/190] [Slash] Fix setter --- contracts/interfaces/ISlashIndicator.sol | 22 ++++----- contracts/slashing/SlashIndicator.sol | 60 ++++++++++++++++++------ 2 files changed, 57 insertions(+), 25 deletions(-) diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index c79570b3d..597f46220 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -26,7 +26,7 @@ interface ISlashIndicator { } /** - * @dev Returns the validator contract. + * @dev Returns the validator contract */ function validatorContract() external view returns (IRoninValidatorSet); @@ -37,7 +37,7 @@ interface ISlashIndicator { * Requirements: * - Only coinbase can call this method * - * Emits the event `ValidatorSlashed`. + * Emits the event `ValidatorSlashed` * */ function slash(address _valAddr) external; @@ -48,13 +48,13 @@ interface ISlashIndicator { * Requirements: * - Only validator contract can call this method * - * Emits the `UnavailabilityIndicatorsReset` events. + * Emits the `UnavailabilityIndicatorsReset` events * */ function resetCounters(address[] calldata) external; /** - * @dev Slashes for double signing. + * @dev Slashes for double signing * * Requirements: * - Only coinbase can call this method @@ -68,7 +68,7 @@ interface ISlashIndicator { * Requirements: * - Only governance admin can call this method * - * Emits the event `SlashThresholdsUpdated`. + * Emits the event `SlashThresholdsUpdated` * */ function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external; @@ -79,7 +79,7 @@ interface ISlashIndicator { * Requirements: * - Only governance admin can call this method * - * Emits the event `SlashFelonyAmountUpdated`. + * Emits the event `SlashFelonyAmountUpdated` * */ function setSlashFelonyAmount(uint256 _slashFelonyAmount) external; @@ -90,7 +90,7 @@ interface ISlashIndicator { * Requirements: * - Only governance admin can call this method * - * Emits the event `SlashDoubleSignAmountUpdated`. + * Emits the event `SlashDoubleSignAmountUpdated` * */ function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external; @@ -101,23 +101,23 @@ interface ISlashIndicator { * Requirements: * - Only governance admin can call this method * - * Emits the event `FelonyJailDurationUpdated`. + * Emits the event `FelonyJailDurationUpdated` * */ function setFelonyJailDuration(uint256 _felonyJailDuration) external; /** - * @dev Gets slash indicator of a validator. + * @dev Gets slash indicator of a validator */ function getSlashIndicator(address _validator) external view returns (uint256); /** - * @dev Gets the slash thresholds. + * @dev Gets the slash thresholds */ function getSlashThresholds() external view returns (uint256 misdemeanorThreshold, uint256 felonyThreshold); /** - * @dev Returns the governance admin address. + * @dev Returns the governance admin address */ function governanceAdmin() external view returns (address); } diff --git a/contracts/slashing/SlashIndicator.sol b/contracts/slashing/SlashIndicator.sol index 3e8503a5b..483c528eb 100644 --- a/contracts/slashing/SlashIndicator.sol +++ b/contracts/slashing/SlashIndicator.sol @@ -68,12 +68,12 @@ contract SlashIndicator is ISlashIndicator, Initializable { uint256 _slashDoubleSignAmount, uint256 _felonyJailBlocks ) external initializer { - misdemeanorThreshold = _misdemeanorThreshold; - felonyThreshold = _felonyThreshold; validatorContract = _validatorSetContract; - slashFelonyAmount = _slashFelonyAmount; - slashDoubleSignAmount = _slashDoubleSignAmount; - felonyJailDuration = _felonyJailBlocks; + + _setSlashThresholds(_felonyThreshold, _misdemeanorThreshold); + _setSlashFelonyAmount(_slashFelonyAmount); + _setSlashDoubleSignAmount(_slashDoubleSignAmount); + _setFelonyJailDuration(_felonyJailBlocks); } /////////////////////////////////////////////////////////////////////////////////////// @@ -146,33 +146,28 @@ contract SlashIndicator is ISlashIndicator, Initializable { override onlyGovernanceAdmin { - felonyThreshold = _felonyThreshold; - misdemeanorThreshold = _misdemeanorThreshold; - emit SlashThresholdsUpdated(_felonyThreshold, _misdemeanorThreshold); + _setSlashThresholds(_felonyThreshold, _misdemeanorThreshold); } /** * @inheritdoc ISlashIndicator */ function setSlashFelonyAmount(uint256 _slashFelonyAmount) external override onlyGovernanceAdmin { - slashFelonyAmount = _slashFelonyAmount; - emit SlashFelonyAmountUpdated(_slashFelonyAmount); + _setSlashFelonyAmount(_slashFelonyAmount); } /** * @inheritdoc ISlashIndicator */ function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external override onlyGovernanceAdmin { - slashDoubleSignAmount = _slashDoubleSignAmount; - emit SlashDoubleSignAmountUpdated(_slashDoubleSignAmount); + _setSlashDoubleSignAmount(_slashDoubleSignAmount); } /** * @inheritdoc ISlashIndicator */ function setFelonyJailDuration(uint256 _felonyJailDuration) external override onlyGovernanceAdmin { - felonyJailDuration = _felonyJailDuration; - emit FelonyJailDurationUpdated(_felonyJailDuration); + _setFelonyJailDuration(_felonyJailDuration); } /////////////////////////////////////////////////////////////////////////////////////// @@ -199,4 +194,41 @@ contract SlashIndicator is ISlashIndicator, Initializable { function governanceAdmin() external view override returns (address) { return _governanceAdmin; } + + /////////////////////////////////////////////////////////////////////////////////////// + // HELPER FUNCTIONS // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @dev Sets the slash thresholds + */ + function _setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) internal { + felonyThreshold = _felonyThreshold; + misdemeanorThreshold = _misdemeanorThreshold; + emit SlashThresholdsUpdated(_felonyThreshold, _misdemeanorThreshold); + } + + /** + * @dev Sets the slash felony amount + */ + function _setSlashFelonyAmount(uint256 _slashFelonyAmount) internal { + slashFelonyAmount = _slashFelonyAmount; + emit SlashFelonyAmountUpdated(_slashFelonyAmount); + } + + /** + * @dev Sets the slash double sign amount + */ + function _setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) internal { + slashDoubleSignAmount = _slashDoubleSignAmount; + emit SlashDoubleSignAmountUpdated(_slashDoubleSignAmount); + } + + /** + * @dev Sets the felony jail duration + */ + function _setFelonyJailDuration(uint256 _felonyJailDuration) internal { + felonyJailDuration = _felonyJailDuration; + emit FelonyJailDurationUpdated(_felonyJailDuration); + } } From 11a0b09dd6c800a9d5f5c5f0be0ffd28f0503e3e Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 13 Sep 2022 17:15:59 +0700 Subject: [PATCH 125/190] [Staking] Cleanup unstake function --- contracts/staking/Staking.sol | 2 +- contracts/staking/StakingManager.sol | 27 +++++++++++++-------------- test/staking/Staking.test.ts | 4 +++- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index bda36ce37..ab4507df8 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -165,7 +165,7 @@ contract Staking is IStaking, StakingManager, Initializable { */ function deductStakingAmount(address _consensusAddr, uint256 _amount) external onlyValidatorContract { ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); - _unstake(_consensusAddr, _candidate.candidateAdmin, _amount, false); + _unstake(_candidate, _candidate.candidateAdmin, _amount); } /** diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index daa3ec189..846b59490 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -85,7 +85,15 @@ abstract contract StakingManager is IStaking, RewardCalculation { */ function unstake(address _consensusAddr, uint256 _amount) external override { address _delegator = msg.sender; - _unstake(_consensusAddr, _delegator, _amount, true); + + ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); + + // TODO: exclude min balance check when staked is deducted by slashing + uint256 remainAmount = _candidate.stakedAmount - _amount; + require(remainAmount >= minValidatorBalance(), "StakingManager: invalid staked amount left"); + + _unstake(_candidate, _delegator, _amount); + // TODO(Thor): replace by `call` and use reentrancy gruard require(payable(msg.sender).send(_amount), "StakingManager: could not transfer RON"); } @@ -167,25 +175,16 @@ abstract contract StakingManager is IStaking, RewardCalculation { * */ function _unstake( - address _poolAddr, + ValidatorCandidate storage _candidate, address _user, - uint256 _amount, - bool _checkMinBalance + uint256 _amount ) internal { - ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); require(_candidate.candidateAdmin == _user, "StakingManager: user is not the candidate admin"); require(_amount <= _candidate.stakedAmount, "StakingManager: insufficient staked amount"); - // TODO: exclude min balance check when staked is deducted by slashing - uint256 remainAmount = _candidate.stakedAmount - _amount; - if (_checkMinBalance) { - require(remainAmount >= minValidatorBalance(), "StakingManager: invalid staked amount left"); - } - _candidate.stakedAmount -= _amount; - emit Unstaked(_poolAddr, _amount); - - _undelegate(_poolAddr, _user, _amount); + emit Unstaked(_candidate.consensusAddr, _amount); + _undelegate(_candidate.consensusAddr, _user, _amount); } /////////////////////////////////////////////////////////////////////////////////////// diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index 593993a44..29cf58b5d 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -164,7 +164,9 @@ describe('Staking test', () => { await expect(stakingContract.stake(poolAddr.address, { value: 1 })).revertedWith( 'StakingManager: user is not the candidate admin' ); - await expect(stakingContract.unstake(poolAddr.address, 1)).revertedWith( + + // TODO: fix unstake value greater than 0 + await expect(stakingContract.unstake(poolAddr.address, 0)).revertedWith( 'StakingManager: user is not the candidate admin' ); }); From 42c1ff436998ea94696e90baf1383c05830a5e2a Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 13 Sep 2022 18:02:15 +0700 Subject: [PATCH 126/190] [IntegrationTest] Add test on contract constructor parameters --- test/integration/Configuration.test.ts | 58 ++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/test/integration/Configuration.test.ts b/test/integration/Configuration.test.ts index 24cccc9c5..229e77d80 100644 --- a/test/integration/Configuration.test.ts +++ b/test/integration/Configuration.test.ts @@ -34,6 +34,8 @@ const slashFelonyAmount = BigNumber.from(1); const slashDoubleSignAmount = 1000; const maxValidatorNumber = 4; const minValidatorBalance = BigNumber.from(100); +const numberOfBlocksInEpoch = 600; +const numberOfEpochsInPeriod = 48; const felonyJailDuration = 28800 * 2; const misdemeanorThreshold = 10; const felonyThreshold = 20; @@ -56,9 +58,9 @@ describe('[Integration] Configuration check', () => { felonyJailBlocks: felonyJailDuration, }; roninValidatorSetConf[network.name] = { - maxValidatorNumber: 21, - numberOfBlocksInEpoch: 600, - numberOfEpochsInPeriod: 48, // 1 day + maxValidatorNumber: maxValidatorNumber, + numberOfBlocksInEpoch: numberOfBlocksInEpoch, + numberOfEpochsInPeriod: numberOfEpochsInPeriod, }; stakingConfig[network.name] = { minValidatorBalance: minValidatorBalance, @@ -92,6 +94,21 @@ describe('[Integration] Configuration check', () => { let _slashingContract = await validatorContract.slashIndicatorContract(); expect(_slashingContract).to.eq(slashContract.address); }); + + it.skip('Should config the maxValidatorNumber correctly', async () => { + // let _maxValidatorNumber= await validatorContract.maxValidatorNumber(); + // expect(_maxValidatorNumber).to.eq(maxValidatorNumber); + }); + + it('Should config the numberOfBlocksInEpoch correctly', async () => { + let _numberOfBlocksInEpoch = await validatorContract.numberOfBlocksInEpoch(); + expect(_numberOfBlocksInEpoch).to.eq(numberOfBlocksInEpoch); + }); + + it('Should config the numberOfEpochsInPeriod correctly', async () => { + let _numberOfEpochsInPeriod = await validatorContract.numberOfEpochsInPeriod(); + expect(_numberOfEpochsInPeriod).to.eq(numberOfEpochsInPeriod); + }); }); describe('StakingContract configuration', async () => { @@ -99,6 +116,16 @@ describe('[Integration] Configuration check', () => { let _validatorSetContract = await stakingContract.validatorContract(); expect(_validatorSetContract).to.eq(validatorContract.address); }); + + it('Should config the minValidatorBalance correctly', async () => { + let _minValidatorBalance = await stakingContract.minValidatorBalance(); + expect(_minValidatorBalance).to.eq(minValidatorBalance); + }); + + it('Should config the maxValidatorCandidate correctly', async () => { + let _maxValidatorCandidate = await stakingContract.maxValidatorCandidate(); + expect(_maxValidatorCandidate).to.eq(maxValidatorNumber); + }); }); describe('SlashIndicatorContract configuration', async () => { @@ -106,5 +133,30 @@ describe('[Integration] Configuration check', () => { let _validatorSetContract = await slashContract.validatorContract(); expect(_validatorSetContract).to.eq(validatorContract.address); }); + + it('Should config the misdemeanorThreshold correctly', async () => { + let _misdemeanorThreshold = await slashContract.misdemeanorThreshold(); + expect(_misdemeanorThreshold).to.eq(misdemeanorThreshold); + }); + + it('Should config the felonyThreshold correctly', async () => { + let _felonyThreshold = await slashContract.felonyThreshold(); + expect(_felonyThreshold).to.eq(felonyThreshold); + }); + + it('Should config the slashFelonyAmount correctly', async () => { + let _slashFelonyAmount = await slashContract.slashFelonyAmount(); + expect(_slashFelonyAmount).to.eq(slashFelonyAmount); + }); + + it('Should config the slashDoubleSignAmount correctly', async () => { + let _slashDoubleSignAmount = await slashContract.slashDoubleSignAmount(); + expect(_slashDoubleSignAmount).to.eq(slashDoubleSignAmount); + }); + + it('Should config the felonyJailDuration correctly', async () => { + let _felonyJailDuration = await slashContract.felonyJailDuration(); + expect(_felonyJailDuration).to.eq(felonyJailDuration); + }); }); }); From c017366e632e6301e91b4ec51810c541f4a97eda Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 13 Sep 2022 18:03:14 +0700 Subject: [PATCH 127/190] [ValidatorSet] Emit events in initializer --- .../ronin-validator/RoninValidatorSet.sol | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index 0a7f6ea3b..75902e157 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -97,9 +97,10 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _slashIndicatorContract = __slashIndicatorContract; _stakingContract = __stakingContract; _stakingVestingContract = __stakingVestingContract; - _maxValidatorNumber = __maxValidatorNumber; - _numberOfBlocksInEpoch = __numberOfBlocksInEpoch; - _numberOfEpochsInPeriod = __numberOfEpochsInPeriod; + + _setMaxValidatorNumber(__maxValidatorNumber); + _setNumberOfBlocksInEpoch(__numberOfBlocksInEpoch); + _setNumberOfEpochsInPeriod(__numberOfEpochsInPeriod); } /////////////////////////////////////////////////////////////////////////////////////// @@ -352,24 +353,21 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { * @inheritdoc IRoninValidatorSet */ function setMaxValidatorNumber(uint256 __maxValidatorNumber) external override onlyGovernanceAdmin { - _maxValidatorNumber = __maxValidatorNumber; - emit MaxValidatorNumberUpdated(__maxValidatorNumber); + _setMaxValidatorNumber(__maxValidatorNumber); } /** * @inheritdoc IRoninValidatorSet */ function setNumberOfBlocksInEpoch(uint256 __numberOfBlocksInEpoch) external override onlyGovernanceAdmin { - _numberOfBlocksInEpoch = __numberOfBlocksInEpoch; - emit NumberOfBlocksInEpochUpdated(__numberOfBlocksInEpoch); + _setNumberOfBlocksInEpoch(__numberOfBlocksInEpoch); } /** * @inheritdoc IRoninValidatorSet */ function setNumberOfEpochsInPeriod(uint256 __numberOfEpochsInPeriod) external override onlyGovernanceAdmin { - _numberOfEpochsInPeriod == __numberOfEpochsInPeriod; - emit NumberOfEpochsInPeriodUpdated(__numberOfEpochsInPeriod); + _setNumberOfEpochsInPeriod(__numberOfEpochsInPeriod); } /////////////////////////////////////////////////////////////////////////////////////// @@ -451,6 +449,30 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { emit ValidatorSetUpdated(_candidates); } + /** + * @dev Updates the max validator number + */ + function _setMaxValidatorNumber(uint256 __maxValidatorNumber) internal { + _maxValidatorNumber = __maxValidatorNumber; + emit MaxValidatorNumberUpdated(__maxValidatorNumber); + } + + /** + * @dev Updates the number of blocks in epoch + */ + function _setNumberOfBlocksInEpoch(uint256 __numberOfBlocksInEpoch) internal { + _numberOfBlocksInEpoch = __numberOfBlocksInEpoch; + emit NumberOfBlocksInEpochUpdated(__numberOfBlocksInEpoch); + } + + /** + * @dev Updates the number of epochs in period + */ + function _setNumberOfEpochsInPeriod(uint256 __numberOfEpochsInPeriod) internal { + _numberOfEpochsInPeriod == __numberOfEpochsInPeriod; + emit NumberOfEpochsInPeriodUpdated(__numberOfEpochsInPeriod); + } + /** * @dev Only receives RON from staking vesting contract. */ From 8d13d9affd82357eed865f5a7cb5e54e8e074b6e Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 13 Sep 2022 18:07:35 +0700 Subject: [PATCH 128/190] [ValidatorSet] Add maxValidatorNumber getter --- contracts/interfaces/IRoninValidatorSet.sol | 5 +++++ contracts/mocks/MockValidatorSet.sol | 2 ++ contracts/ronin-validator/RoninValidatorSet.sol | 7 +++++++ test/integration/Configuration.test.ts | 6 +++--- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index 8536c139e..299d753b6 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -125,6 +125,11 @@ interface IRoninValidatorSet { */ function numberOfBlocksInEpoch() external view returns (uint256 _numberOfBlocks); + /** + * @dev Returns the maximum number of validators in the epoch + */ + function maxValidatorNumber() external view returns (uint256 _maximumValidatorNumber); + /** * @dev Returns the epoch index from the block number. */ diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index 2920cb7df..8c2cb5a51 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -101,4 +101,6 @@ contract MockValidatorSet is IRoninValidatorSet { function setNumberOfBlocksInEpoch(uint256 _numberOfBlocksInEpoch) external override {} function setNumberOfEpochsInPeriod(uint256 _numberOfEpochsInPeriod) external override {} + + function maxValidatorNumber() external view override returns (uint256 _maximumValidatorNumber) {} } diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index 75902e157..9eb6ff416 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -333,6 +333,13 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { return _numberOfBlocksInEpoch; } + /** + * @inheritdoc IRoninValidatorSet + */ + function maxValidatorNumber() external view override returns (uint256 _maximumValidatorNumber) { + return _maxValidatorNumber; + } + /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR GOVERNANCE ADMIN // /////////////////////////////////////////////////////////////////////////////////////// diff --git a/test/integration/Configuration.test.ts b/test/integration/Configuration.test.ts index 229e77d80..071eceab9 100644 --- a/test/integration/Configuration.test.ts +++ b/test/integration/Configuration.test.ts @@ -95,9 +95,9 @@ describe('[Integration] Configuration check', () => { expect(_slashingContract).to.eq(slashContract.address); }); - it.skip('Should config the maxValidatorNumber correctly', async () => { - // let _maxValidatorNumber= await validatorContract.maxValidatorNumber(); - // expect(_maxValidatorNumber).to.eq(maxValidatorNumber); + it('Should config the maxValidatorNumber correctly', async () => { + let _maxValidatorNumber = await validatorContract.maxValidatorNumber(); + expect(_maxValidatorNumber).to.eq(maxValidatorNumber); }); it('Should config the numberOfBlocksInEpoch correctly', async () => { From 8eb5d787ee3e87906a311bd18ce00c39f7f99c5e Mon Sep 17 00:00:00 2001 From: nxqbao Date: Tue, 13 Sep 2022 18:08:13 +0700 Subject: [PATCH 129/190] [ValidatorSet] Fix initializer --- contracts/ronin-validator/RoninValidatorSet.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index 9eb6ff416..9feaf5880 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -476,7 +476,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { * @dev Updates the number of epochs in period */ function _setNumberOfEpochsInPeriod(uint256 __numberOfEpochsInPeriod) internal { - _numberOfEpochsInPeriod == __numberOfEpochsInPeriod; + _numberOfEpochsInPeriod = __numberOfEpochsInPeriod; emit NumberOfEpochsInPeriodUpdated(__numberOfEpochsInPeriod); } From ac55a6e642e6ad5615d21b8c5d722c3b2cb33071 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Wed, 14 Sep 2022 10:02:31 +0700 Subject: [PATCH 130/190] chore: remove outdated TODO --- contracts/staking/StakingManager.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 846b59490..965ed4b82 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -88,7 +88,6 @@ abstract contract StakingManager is IStaking, RewardCalculation { ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); - // TODO: exclude min balance check when staked is deducted by slashing uint256 remainAmount = _candidate.stakedAmount - _amount; require(remainAmount >= minValidatorBalance(), "StakingManager: invalid staked amount left"); From 78515832cb39a163342419e15b2c74daa882a3c7 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Wed, 14 Sep 2022 10:18:04 +0700 Subject: [PATCH 131/190] chore: fix typo --- test/helpers/reward-calculation.ts | 12 ++++++------ test/helpers/slash-indicator.ts | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/helpers/reward-calculation.ts b/test/helpers/reward-calculation.ts index 293cd3e1d..107fe3825 100644 --- a/test/helpers/reward-calculation.ts +++ b/test/helpers/reward-calculation.ts @@ -9,19 +9,19 @@ const contractInterface = RewardCalculation__factory.createInterface(); export const expects = { emitSettledPoolsUpdatedEvent: async function ( tx: ContractTransaction, - expectedPoolList?: string[], - expectedAccumulatedRpsList?: BigNumberish[] + expectingPoolList?: string[], + expectingAccumulatedRpsList?: BigNumberish[] ) { await expectEvent( contractInterface, 'SettledPoolsUpdated', tx, (event) => { - if (expectedPoolList) { - expect(event.args[0], 'invalid pool list').eql(expectedPoolList); + if (expectingPoolList) { + expect(event.args[0], 'invalid pool list').eql(expectingPoolList); } - if (expectedAccumulatedRpsList) { - expect(event.args[1], 'invalid accumulated rps list').eql(expectedAccumulatedRpsList); + if (expectingAccumulatedRpsList) { + expect(event.args[1], 'invalid accumulated rps list').eql(expectingAccumulatedRpsList); } }, 1 diff --git a/test/helpers/slash-indicator.ts b/test/helpers/slash-indicator.ts index d8a58f336..329b23b24 100644 --- a/test/helpers/slash-indicator.ts +++ b/test/helpers/slash-indicator.ts @@ -7,14 +7,14 @@ import { ISlashIndicator__factory } from '../../src/types'; const contractInterface = ISlashIndicator__factory.createInterface(); export const expects = { - emitUnavailabilityIndicatorsResetEvent: async function (tx: ContractTransaction, expectedValidatorList?: string[]) { + emitUnavailabilityIndicatorsResetEvent: async function (tx: ContractTransaction, expectingValidatorList?: string[]) { await expectEvent( contractInterface, 'UnavailabilityIndicatorsReset', tx, (event) => { - if (expectedValidatorList) { - expect(event.args[0], 'invalid reset validator list').eql(expectedValidatorList); + if (expectingValidatorList) { + expect(event.args[0], 'invalid reset validator list').eql(expectingValidatorList); } }, 1 From a32d1043f40a261a630a83ea364c0ee75688fe55 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Wed, 14 Sep 2022 10:27:14 +0700 Subject: [PATCH 132/190] [Staking] Add setGovernanceAdmin function --- contracts/interfaces/IStaking.sol | 11 +++++++++++ contracts/staking/Staking.sol | 16 ++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 010e05e57..fada079f3 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -76,6 +76,17 @@ interface IStaking is IRewardPool { */ function minValidatorBalance() external view returns (uint256); + /** + * @dev Updates the governance admin + * + * Requirements: + * - The method caller is the governance admin + * + * Emits the event `GovernanceAdminUpdated` + * + */ + function setGovernanceAdmin(address _governanceAdmin) external; + /** * @dev Sets the minimum threshold for being a validator candidate. * diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index ab4507df8..8466e88f7 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -54,7 +54,7 @@ contract Staking is IStaking, StakingManager, Initializable { uint256 __minValidatorBalance ) external initializer { _setValidatorContract(__validatorContract); - _setGovernanceAdminAddress(__governanceAdmin); + _setGovernanceAdmin(__governanceAdmin); _setMaxValidatorCandidate(__maxValidatorCandidate); _setMinValidatorBalance(__minValidatorBalance); } @@ -74,6 +74,13 @@ contract Staking is IStaking, StakingManager, Initializable { return _validatorContract; } + /** + * @inheritdoc IStaking + */ + function setGovernanceAdmin(address _newAddr) external override onlyGovernanceAdmin { + _setGovernanceAdmin(_newAddr); + } + /** * @inheritdoc IStaking */ @@ -84,7 +91,7 @@ contract Staking is IStaking, StakingManager, Initializable { /** * @inheritdoc IStaking */ - function setMinValidatorBalance(uint256 _threshold) external onlyGovernanceAdmin { + function setMinValidatorBalance(uint256 _threshold) external override onlyGovernanceAdmin { _setMinValidatorBalance(_threshold); } @@ -98,7 +105,7 @@ contract Staking is IStaking, StakingManager, Initializable { /** * @inheritdoc IStaking */ - function setMaxValidatorCandidate(uint256 _threshold) external onlyGovernanceAdmin { + function setMaxValidatorCandidate(uint256 _threshold) external override onlyGovernanceAdmin { _setMaxValidatorCandidate(_threshold); } @@ -222,7 +229,8 @@ contract Staking is IStaking, StakingManager, Initializable { * Emits the `GovernanceAdminUpdated` event. * */ - function _setGovernanceAdminAddress(address _newAddr) internal { + function _setGovernanceAdmin(address _newAddr) internal { + require(_newAddr != address(0), "Staking: Cannot set admin to zero address"); _governanceAdmin = _newAddr; emit GovernanceAdminUpdated(_newAddr); } From f35b5eeda0dd7cc60feb805371db0bc4d41e8373 Mon Sep 17 00:00:00 2001 From: nxqbao Date: Wed, 14 Sep 2022 10:28:35 +0700 Subject: [PATCH 133/190] [ValidatorSet] Add setGovernanceAdmin function --- .../ronin-validator/RoninValidatorSet.sol | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index 9feaf5880..db28c6c27 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -93,7 +93,8 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { uint256 __numberOfBlocksInEpoch, uint256 __numberOfEpochsInPeriod ) external initializer { - _governanceAdmin = __governanceAdmin; + _setGovernanceAdmin(__governanceAdmin); + _slashIndicatorContract = __slashIndicatorContract; _stakingContract = __stakingContract; _stakingVestingContract = __stakingVestingContract; @@ -348,12 +349,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { * @inheritdoc IRoninValidatorSet */ function setGovernanceAdmin(address __governanceAdmin) external override onlyGovernanceAdmin { - if (__governanceAdmin == address(0) || __governanceAdmin == _governanceAdmin) { - return; - } - - _governanceAdmin == __governanceAdmin; - emit GovernanceAdminUpdated(__governanceAdmin); + _setGovernanceAdmin(__governanceAdmin); } /** @@ -456,6 +452,20 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { emit ValidatorSetUpdated(_candidates); } + /** + * @dev Updates the address of governance admin + */ + function _setGovernanceAdmin(address __governanceAdmin) internal { + if (__governanceAdmin == _governanceAdmin) { + return; + } + + require(__governanceAdmin != address(0), "RoninValidatorSet: Cannot set admin to zero address"); + + _governanceAdmin == __governanceAdmin; + emit GovernanceAdminUpdated(__governanceAdmin); + } + /** * @dev Updates the max validator number */ From d763e04b1c83562e1a1d012e38796d6241e0cbbf Mon Sep 17 00:00:00 2001 From: Bao Date: Wed, 14 Sep 2022 10:57:51 +0700 Subject: [PATCH 134/190] [Slash] Add governanceAdmin in initializing contract (#9) ### Description **Changes to Slashing contract** - Add `governanceAdmin` in `initializing` function of contract - Add setter/getter for `governanceAdmin` ### Checklist - [x] I have clearly commented on all the main functions followed the [NatSpec Format](https://docs.soliditylang.org/en/v0.8.0/natspec-format.html) - [x] The box that allows repo maintainers to update this PR is checked - [x] I tested locally to make sure this feature/fix works --- contracts/interfaces/ISlashIndicator.sol | 13 ++++++++++ contracts/mocks/MockSlashIndicator.sol | 2 ++ contracts/slashing/SlashIndicator.sol | 26 +++++++++++++++++-- src/deploy/proxy/slash-indicator-proxy.ts | 3 ++- .../integration/ActionSlashValidators.test.ts | 3 ++- test/integration/ActionSubmitReward.test.ts | 3 ++- test/integration/ActionWrapUpEpoch.test.ts | 3 ++- test/slash/SlashIndicator.test.ts | 8 +++--- 8 files changed, 52 insertions(+), 9 deletions(-) diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index 597f46220..c9b631d2b 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -17,6 +17,8 @@ interface ISlashIndicator { event SlashDoubleSignAmountUpdated(uint256 slashDoubleSignAmount); /// @dev Emiited when the duration of jailing felony updated event FelonyJailDurationUpdated(uint256 felonyJailDuration); + /// @dev Emitted when the address of governance admin is updated. + event GovernanceAdminUpdated(address); enum SlashType { UNKNOWN, @@ -106,6 +108,17 @@ interface ISlashIndicator { */ function setFelonyJailDuration(uint256 _felonyJailDuration) external; + /** + * @dev Updates the governance admin + * + * Requirements: + * - The method caller is the governance admin + * + * Emits the event `GovernanceAdminUpdated` + * + */ + function setGovernanceAdmin(address _governanceAdmin) external; + /** * @dev Gets slash indicator of a validator */ diff --git a/contracts/mocks/MockSlashIndicator.sol b/contracts/mocks/MockSlashIndicator.sol index 0d36a3ad1..bb764c950 100644 --- a/contracts/mocks/MockSlashIndicator.sol +++ b/contracts/mocks/MockSlashIndicator.sol @@ -52,6 +52,8 @@ contract MockSlashIndicator is ISlashIndicator { function governanceAdmin() external view override returns (address) {} + function setGovernanceAdmin(address __newAddr) external override {} + function setSlashFelonyAmount(uint256 _slashFelonyAmount) external override {} function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external override {} diff --git a/contracts/slashing/SlashIndicator.sol b/contracts/slashing/SlashIndicator.sol index 483c528eb..d50000d5d 100644 --- a/contracts/slashing/SlashIndicator.sol +++ b/contracts/slashing/SlashIndicator.sol @@ -61,15 +61,16 @@ contract SlashIndicator is ISlashIndicator, Initializable { * @dev Initializes the contract storage. */ function initialize( + address __governanceAdmin, + IRoninValidatorSet _validatorSetContract, uint256 _misdemeanorThreshold, uint256 _felonyThreshold, - IRoninValidatorSet _validatorSetContract, uint256 _slashFelonyAmount, uint256 _slashDoubleSignAmount, uint256 _felonyJailBlocks ) external initializer { validatorContract = _validatorSetContract; - + _setGovernanceAdmin(__governanceAdmin); _setSlashThresholds(_felonyThreshold, _misdemeanorThreshold); _setSlashFelonyAmount(_slashFelonyAmount); _setSlashDoubleSignAmount(_slashDoubleSignAmount); @@ -138,6 +139,13 @@ contract SlashIndicator is ISlashIndicator, Initializable { // GOVERNANCE FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// + /** + * @inheritdoc ISlashIndicator + */ + function setGovernanceAdmin(address __governanceAdmin) external override onlyGovernanceAdmin { + _setGovernanceAdmin(__governanceAdmin); + } + /** * @inheritdoc ISlashIndicator */ @@ -199,6 +207,20 @@ contract SlashIndicator is ISlashIndicator, Initializable { // HELPER FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// + /** + * @dev Updates the address of governance admin + */ + function _setGovernanceAdmin(address __governanceAdmin) internal { + if (__governanceAdmin == _governanceAdmin) { + return; + } + + require(__governanceAdmin != address(0), "SlashIndicator: Cannot set admin to zero address"); + + _governanceAdmin == __governanceAdmin; + emit GovernanceAdminUpdated(__governanceAdmin); + } + /** * @dev Sets the slash thresholds */ diff --git a/src/deploy/proxy/slash-indicator-proxy.ts b/src/deploy/proxy/slash-indicator-proxy.ts index 218592aef..e65819853 100644 --- a/src/deploy/proxy/slash-indicator-proxy.ts +++ b/src/deploy/proxy/slash-indicator-proxy.ts @@ -11,9 +11,10 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const logicContract = await deployments.get('SlashIndicatorLogic'); const data = new SlashIndicator__factory().interface.encodeFunctionData('initialize', [ + initAddress[network.name]!.governanceAdmin, + initAddress[network.name]!.validatorContract, slashIndicatorConf[network.name]!.misdemeanorThreshold, slashIndicatorConf[network.name]!.felonyThreshold, - initAddress[network.name]!.validatorContract, slashIndicatorConf[network.name]!.slashFelonyAmount, slashIndicatorConf[network.name]!.slashDoubleSignAmount, slashIndicatorConf[network.name]!.felonyJailBlocks, diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index 938b0e3ed..3ab052b77 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -76,9 +76,10 @@ describe('[Integration] Slash validators', () => { slashLogicContract.address, proxyAdmin.address, slashLogicContract.interface.encodeFunctionData('initialize', [ + governanceAdmin.address, + roninValidatorSetAddr, slashIndicatorConf[network.name]!.misdemeanorThreshold, slashIndicatorConf[network.name]!.felonyThreshold, - roninValidatorSetAddr, slashIndicatorConf[network.name]!.slashFelonyAmount, slashIndicatorConf[network.name]!.slashDoubleSignAmount, slashIndicatorConf[network.name]!.felonyJailBlocks, diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index f0ea27f2e..f031a5cdd 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -75,9 +75,10 @@ describe('[Integration] Submit Block Reward', () => { slashLogicContract.address, proxyAdmin.address, slashLogicContract.interface.encodeFunctionData('initialize', [ + governanceAdmin.address, + roninValidatorSetAddr, slashIndicatorConf[network.name]!.misdemeanorThreshold, slashIndicatorConf[network.name]!.felonyThreshold, - roninValidatorSetAddr, slashIndicatorConf[network.name]!.slashFelonyAmount, slashIndicatorConf[network.name]!.slashDoubleSignAmount, slashIndicatorConf[network.name]!.felonyJailBlocks, diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index 68921c4f5..6b6d550fa 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -76,9 +76,10 @@ describe('[Integration] Wrap up epoch', () => { slashLogicContract.address, proxyAdmin.address, slashLogicContract.interface.encodeFunctionData('initialize', [ + governanceAdmin.address, + roninValidatorSetAddr, slashIndicatorConf[network.name]!.misdemeanorThreshold, slashIndicatorConf[network.name]!.felonyThreshold, - roninValidatorSetAddr, slashIndicatorConf[network.name]!.slashFelonyAmount, slashIndicatorConf[network.name]!.slashDoubleSignAmount, slashIndicatorConf[network.name]!.felonyJailBlocks, diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index 55b0531ce..793116ae5 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -12,7 +12,7 @@ import { import { Address } from 'hardhat-deploy/dist/types'; import { SlashType } from './slashType'; import { Network, slashIndicatorConf } from '../../src/config'; -import { BigNumber } from 'ethers'; +import { BigNumber, Signer } from 'ethers'; import { expects as SlashExpects } from '../helpers/slash-indicator'; let slashContract: SlashIndicator; @@ -21,6 +21,7 @@ let deployer: SignerWithAddress; let proxyAdmin: SignerWithAddress; let mockValidatorsContract: MockValidatorSetForSlash; let vagabond: SignerWithAddress; +let governanceAdmin: SignerWithAddress; let coinbases: SignerWithAddress[]; let defaultCoinbase: Address; let localIndicators: number[]; @@ -55,7 +56,7 @@ describe('Slash indicator test', () => { let misdemeanorThreshold: number; before(async () => { - [deployer, proxyAdmin, vagabond, ...coinbases] = await ethers.getSigners(); + [deployer, proxyAdmin, vagabond, governanceAdmin, ...coinbases] = await ethers.getSigners(); localIndicators = Array(coinbases.length).fill(0); defaultCoinbase = await network.provider.send('eth_coinbase'); @@ -75,9 +76,10 @@ describe('Slash indicator test', () => { logicContract.address, proxyAdmin.address, logicContract.interface.encodeFunctionData('initialize', [ + governanceAdmin.address, + mockValidatorsContract.address, slashIndicatorConf[network.name]!.misdemeanorThreshold, slashIndicatorConf[network.name]!.felonyThreshold, - mockValidatorsContract.address, slashIndicatorConf[network.name]!.slashFelonyAmount, slashIndicatorConf[network.name]!.slashDoubleSignAmount, slashIndicatorConf[network.name]!.felonyJailBlocks, From b65b5d4458162f0b84d2457490b3213fe63a83b2 Mon Sep 17 00:00:00 2001 From: Bao Date: Wed, 14 Sep 2022 16:59:07 +0700 Subject: [PATCH 135/190] Add `fixture` for integration test. Add `bonusAmount` for `BlockRewardSubmitted` event (#10) * [IntegrationTest] Update fixture for slash validators * [IntegrationTest] Update fixture for submit reward * [IntegrationTest] Fix deployment script in tests * [IntegrationTest] Update fixture for wrap up epoch * [Test] Fix RoninValidator test to adapt new mock contract * chore: remove TODO * [RoninValidator] Add bonusAmount for BlockRewardSubmitted event --- contracts/interfaces/IRoninValidatorSet.sol | 2 +- .../MockRoninValidatorSetEpochSetter.sol | 14 +- .../ronin-validator/RoninValidatorSet.sol | 11 +- contracts/staking/Staking.sol | 2 +- contracts/staking/StakingManager.sol | 2 +- test/helpers/ronin-validator-set.ts | 8 +- .../integration/ActionSlashValidators.test.ts | 130 ++++++++---------- test/integration/ActionSubmitReward.test.ts | 130 ++++++++---------- test/integration/ActionWrapUpEpoch.test.ts | 125 ++++++++--------- test/validator/RoninValidatorSet.test.ts | 99 +++++++++---- 10 files changed, 259 insertions(+), 264 deletions(-) diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index 299d753b6..4b0d3ff40 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -8,7 +8,7 @@ interface IRoninValidatorSet { /// @dev Emitted when the reward of the valdiator is deprecated. event RewardDeprecated(address coinbaseAddr, uint256 rewardAmount); /// @dev Emitted when the block reward is submitted. - event BlockRewardSubmitted(address coinbaseAddr, uint256 rewardAmount); + event BlockRewardSubmitted(address coinbaseAddr, uint256 submittedAmount, uint256 bonusAmount); /// @dev Emitted when the validator is slashed. event ValidatorSlashed(address validatorAddr, uint256 jailedUntil, uint256 deductedStakingAmount); /// @dev Emitted when the validator reward is distributed. diff --git a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol index 91a246c8c..03a8f6c12 100644 --- a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol +++ b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol @@ -8,19 +8,7 @@ contract MockRoninValidatorSetEpochSetter is RoninValidatorSet { uint256[] internal _epochs; uint256[] internal _periods; - constructor( - address __governanceAdmin, - address __slashIndicatorContract, - address __stakingContract, - address __stakingVestingContract, - uint256 __maxValidatorNumber - ) { - _governanceAdmin = __governanceAdmin; - _slashIndicatorContract = __slashIndicatorContract; - _stakingContract = __stakingContract; - _stakingVestingContract = __stakingVestingContract; - _maxValidatorNumber = __maxValidatorNumber; - } + constructor() RoninValidatorSet() {} function endEpoch() external { _epochs.push(block.number); diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index db28c6c27..1c47ef7f9 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -112,8 +112,8 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { * @inheritdoc IRoninValidatorSet */ function submitBlockReward() external payable override onlyCoinbase { - uint256 _reward = msg.value; - if (_reward == 0) { + uint256 _submittedReward = msg.value; + if (_submittedReward == 0) { return; } @@ -122,11 +122,12 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { if ( !_isValidator(_coinbaseAddr) || _jailed(_coinbaseAddr) || _rewardDeprecated(_coinbaseAddr, periodOf(block.number)) ) { - emit RewardDeprecated(_coinbaseAddr, _reward); + emit RewardDeprecated(_coinbaseAddr, _submittedReward); return; } - _reward += IStakingVesting(_stakingVestingContract).requestBlockBonus(); + uint256 _bonusReward = IStakingVesting(_stakingVestingContract).requestBlockBonus(); + uint256 _reward = _submittedReward + _bonusReward; IStaking _staking = IStaking(_stakingContract); uint256 _rate = _staking.commissionRateOf(_coinbaseAddr); @@ -136,7 +137,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _miningReward[_coinbaseAddr] += _miningAmount; _delegatingReward[_coinbaseAddr] += _delegatingAmount; _staking.recordReward(_coinbaseAddr, _delegatingAmount); - emit BlockRewardSubmitted(_coinbaseAddr, _reward); + emit BlockRewardSubmitted(_coinbaseAddr, _submittedReward, _bonusReward); } /** diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 8466e88f7..8bed21782 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -15,7 +15,7 @@ contract Staking is IStaking, StakingManager, Initializable { /// @dev Maximum number of validator. uint256 internal _maxValidatorCandidate; /// @dev Governance admin address. - address internal _governanceAdmin; // TODO(Thor): add setter. + address internal _governanceAdmin; /// @dev Validator contract address. address internal _validatorContract; // Change type to address for testing purpose diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 965ed4b82..ad068f8cd 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -128,7 +128,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { address _candidateAdmin ) internal returns (uint256 _candidateIdx) { uint256 _length = getValidatorCandidateLength(); - require(_length < maxValidatorCandidate(), "StakingManager: query for exceeded validator array length"); + require(_length < maxValidatorCandidate(), "StakingManager: exceeds maximum number of candidates"); require(_getCandidateIndex(_consensusAddr) == 0, "StakingManager: query for existed candidate"); require(_amount >= minValidatorBalance(), "StakingManager: insufficient amount"); // TODO(Thor): replace by `call` and use reentrancy gruard diff --git a/test/helpers/ronin-validator-set.ts b/test/helpers/ronin-validator-set.ts index d69fd213a..b302dab7b 100644 --- a/test/helpers/ronin-validator-set.ts +++ b/test/helpers/ronin-validator-set.ts @@ -27,7 +27,8 @@ export const expects = { emitBlockRewardSubmittedEvent: async function ( tx: ContractTransaction, expectingCoinbaseAddr: string, - expectingDeprecatedReward: BigNumberish + expectingSubmittedReward: BigNumberish, + expectingBonusReward: BigNumberish ) { await expectEvent( contractInterface, @@ -35,7 +36,8 @@ export const expects = { tx, (event) => { expect(event.args[0], 'invalid coinbase address').eq(expectingCoinbaseAddr); - expect(event.args[1], 'invalid reward').eq(expectingDeprecatedReward); + expect(event.args[1], 'invalid submitted reward').eq(expectingSubmittedReward); + expect(event.args[2], 'invalid bonus reward').eq(expectingBonusReward); }, 1 ); @@ -83,7 +85,7 @@ export const expects = { 'StakingRewardDistributed', tx, (event) => { - expect(event.args[0], 'invalid deprecated reward').eq(expectingAmount); + expect(event.args[0], 'invalid distributing reward').eq(expectingAmount); }, 1 ); diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index 3ab052b77..f385b3638 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { network, ethers } from 'hardhat'; +import { network, ethers, deployments, getNamedAccounts } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { @@ -9,10 +9,16 @@ import { Staking__factory, MockRoninValidatorSetEpochSetter__factory, MockRoninValidatorSetEpochSetter, - TransparentUpgradeableProxy__factory, - StakingVesting__factory, + ProxyAdmin__factory, } from '../../src/types'; -import { Network, slashIndicatorConf, roninValidatorSetConf, stakingConfig, initAddress } from '../../src/config'; +import { + Network, + slashIndicatorConf, + roninValidatorSetConf, + stakingConfig, + stakingVestingConfig, + initAddress, +} from '../../src/config'; import { BigNumber, ContractTransaction } from 'ethers'; import { SlashType } from '../slash/slashType'; import { expects as RoninValidatorSetExpects } from '../helpers/ronin-validator-set'; @@ -26,21 +32,27 @@ let validatorContract: MockRoninValidatorSetEpochSetter; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; let governanceAdmin: SignerWithAddress; -let proxyAdmin: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; -const slashFelonyAmount = BigNumber.from(1); -const slashDoubleSignAmount = 1000; -const maxValidatorNumber = 4; -const minValidatorBalance = BigNumber.from(100); const felonyJailDuration = 28800 * 2; const misdemeanorThreshold = 10; const felonyThreshold = 20; +const slashFelonyAmount = BigNumber.from(1); +const slashDoubleSignAmount = 1000; + +const maxValidatorNumber = 3; +const numberOfBlocksInEpoch = 600; +const numberOfEpochsInPeriod = 48; + +const minValidatorBalance = BigNumber.from(100); +const maxValidatorCandidate = 10; + +const bonusPerBlock = BigNumber.from(1); +const topUpAmount = BigNumber.from(10000); describe('[Integration] Slash validators', () => { before(async () => { - [coinbase, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); - validatorCandidates = validatorCandidates.slice(0, 5); + [deployer, coinbase, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); if (network.name == Network.Hardhat) { @@ -55,73 +67,48 @@ describe('[Integration] Slash validators', () => { felonyJailBlocks: felonyJailDuration, }; roninValidatorSetConf[network.name] = { - maxValidatorNumber: 21, - numberOfBlocksInEpoch: 600, - numberOfEpochsInPeriod: 48, // 1 day + maxValidatorNumber: maxValidatorNumber, + numberOfBlocksInEpoch: numberOfBlocksInEpoch, + numberOfEpochsInPeriod: numberOfEpochsInPeriod, }; stakingConfig[network.name] = { minValidatorBalance: minValidatorBalance, - maxValidatorCandidate: maxValidatorNumber, + maxValidatorCandidate: maxValidatorCandidate, + }; + stakingVestingConfig[network.name] = { + bonusPerBlock: bonusPerBlock, + topupAmount: topUpAmount, }; } - const nonce = await deployer.getTransactionCount(); - const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); - const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 6 }); - - const slashLogicContract = await new SlashIndicator__factory(deployer).deploy(); - await slashLogicContract.deployed(); - - const slashProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( - slashLogicContract.address, - proxyAdmin.address, - slashLogicContract.interface.encodeFunctionData('initialize', [ - governanceAdmin.address, - roninValidatorSetAddr, - slashIndicatorConf[network.name]!.misdemeanorThreshold, - slashIndicatorConf[network.name]!.felonyThreshold, - slashIndicatorConf[network.name]!.slashFelonyAmount, - slashIndicatorConf[network.name]!.slashDoubleSignAmount, - slashIndicatorConf[network.name]!.felonyJailBlocks, - ]) - ); - await slashProxyContract.deployed(); - slashContract = SlashIndicator__factory.connect(slashProxyContract.address, deployer); - - const stakingVestingLogic = await new StakingVesting__factory(deployer).deploy(); - const stakingVesting = await new TransparentUpgradeableProxy__factory(deployer).deploy( - stakingVestingLogic.address, - proxyAdmin.address, - stakingVestingLogic.interface.encodeFunctionData('initialize', [0, roninValidatorSetAddr]) + await deployments.fixture([ + 'ProxyAdmin', + 'CalculateAddresses', + 'RoninValidatorSetProxy', + 'SlashIndicatorProxy', + 'StakingProxy', + 'StakingVestingProxy', + ]); + + const slashContractDeployment = await deployments.get('SlashIndicatorProxy'); + slashContract = SlashIndicator__factory.connect(slashContractDeployment.address, deployer); + + const stakingContractDeployment = await deployments.get('StakingProxy'); + stakingContract = Staking__factory.connect(stakingContractDeployment.address, deployer); + + const validatorContractDeployment = await deployments.get('RoninValidatorSetProxy'); + validatorContract = MockRoninValidatorSetEpochSetter__factory.connect( + validatorContractDeployment.address, + deployer ); - validatorContract = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy( - governanceAdmin.address, - slashContract.address, - stakingContractAddr, - stakingVesting.address, - maxValidatorNumber - ); - await validatorContract.deployed(); - - const stakingLogicContract = await new Staking__factory(deployer).deploy(); - await stakingLogicContract.deployed(); - - const stakingProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( - stakingLogicContract.address, - proxyAdmin.address, - stakingLogicContract.interface.encodeFunctionData('initialize', [ - validatorContract.address, - governanceAdmin.address, - 100, - minValidatorBalance, - ]) - ); - await stakingProxyContract.deployed(); - stakingContract = Staking__factory.connect(stakingProxyContract.address, deployer); + const mockValidatorLogic = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy(); + await mockValidatorLogic.deployed(); - expect(roninValidatorSetAddr.toLowerCase()).eq(validatorContract.address.toLowerCase()); - expect(stakingContractAddr.toLowerCase()).eq(stakingContract.address.toLowerCase()); + const proxyAdminDeployment = await deployments.get('ProxyAdmin'); + let proxyAdminContract = ProxyAdmin__factory.connect(proxyAdminDeployment.address, deployer); + + await proxyAdminContract.upgrade(validatorContract.address, mockValidatorLogic.address); }); describe('Configuration test', async () => { @@ -166,7 +153,6 @@ describe('[Integration] Slash validators', () => { let tx = slashContract.connect(coinbase).slash(slashee.address); await expect(tx).to.emit(slashContract, 'ValidatorSlashed').withArgs(slashee.address, SlashType.MISDEMEANOR); - await expect(tx).to.emit(validatorContract, 'ValidatorSlashed').withArgs(slashee.address, 0, 0); }); }); @@ -380,12 +366,12 @@ describe('[Integration] Slash validators', () => { await validatorContract.connect(coinbase).endEpoch(); updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); - // TODO(thor): Fix this assertion when update contract to check minimum amount on updating validator set + // FIXME: Fix this assertion when update contract to check minimum amount on updating validator set expectingValidatorSet.push(slashee.address); }); it.skip('Should the validator candidate cannot join as a validator when jail time is over, due to insufficient fund', async () => { - // TODO(thor): Fix this assertion when update contract to check minimum amount on updating validator set + // FIXME: Fix this assertion when update contract to check minimum amount on updating validator set // expectingValidatorSet.pop(); await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); }); diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index f031a5cdd..455fef6e4 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { network, ethers } from 'hardhat'; +import { network, ethers, deployments } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs'; @@ -10,10 +10,16 @@ import { Staking__factory, MockRoninValidatorSetEpochSetter__factory, MockRoninValidatorSetEpochSetter, - TransparentUpgradeableProxy__factory, - StakingVesting__factory, + ProxyAdmin__factory, } from '../../src/types'; -import { Network, slashIndicatorConf, roninValidatorSetConf, stakingConfig, initAddress } from '../../src/config'; +import { + Network, + slashIndicatorConf, + roninValidatorSetConf, + stakingConfig, + initAddress, + stakingVestingConfig, +} from '../../src/config'; import { BigNumber, ContractTransaction } from 'ethers'; import { mineBatchTxs } from '../utils'; @@ -24,22 +30,29 @@ let validatorContract: MockRoninValidatorSetEpochSetter; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; let governanceAdmin: SignerWithAddress; -let proxyAdmin: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; -const slashFelonyAmount = BigNumber.from(1); -const slashDoubleSignAmount = 1000; -const maxValidatorNumber = 4; -const minValidatorBalance = BigNumber.from(100); const felonyJailDuration = 28800 * 2; const misdemeanorThreshold = 10; const felonyThreshold = 20; -const blockRewardAmount = BigNumber.from(2); +const slashFelonyAmount = BigNumber.from(1); +const slashDoubleSignAmount = 1000; + +const maxValidatorNumber = 3; +const numberOfBlocksInEpoch = 600; +const numberOfEpochsInPeriod = 48; + +const minValidatorBalance = BigNumber.from(100); +const maxValidatorCandidate = 10; + +const bonusPerBlock = BigNumber.from(1); +const topUpAmount = BigNumber.from(10000); describe('[Integration] Submit Block Reward', () => { + const blockRewardAmount = BigNumber.from(2); + before(async () => { - [coinbase, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); - validatorCandidates = validatorCandidates.slice(0, 5); + [deployer, coinbase, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); if (network.name == Network.Hardhat) { @@ -54,73 +67,48 @@ describe('[Integration] Submit Block Reward', () => { felonyJailBlocks: felonyJailDuration, }; roninValidatorSetConf[network.name] = { - maxValidatorNumber: 21, - numberOfBlocksInEpoch: 600, - numberOfEpochsInPeriod: 48, // 1 day + maxValidatorNumber: maxValidatorNumber, + numberOfBlocksInEpoch: numberOfBlocksInEpoch, + numberOfEpochsInPeriod: numberOfEpochsInPeriod, }; stakingConfig[network.name] = { minValidatorBalance: minValidatorBalance, - maxValidatorCandidate: maxValidatorNumber, + maxValidatorCandidate: maxValidatorCandidate, + }; + stakingVestingConfig[network.name] = { + bonusPerBlock: bonusPerBlock, + topupAmount: topUpAmount, }; } - const nonce = await deployer.getTransactionCount(); - const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); - const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 6 }); - - const slashLogicContract = await new SlashIndicator__factory(deployer).deploy(); - await slashLogicContract.deployed(); - - const slashProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( - slashLogicContract.address, - proxyAdmin.address, - slashLogicContract.interface.encodeFunctionData('initialize', [ - governanceAdmin.address, - roninValidatorSetAddr, - slashIndicatorConf[network.name]!.misdemeanorThreshold, - slashIndicatorConf[network.name]!.felonyThreshold, - slashIndicatorConf[network.name]!.slashFelonyAmount, - slashIndicatorConf[network.name]!.slashDoubleSignAmount, - slashIndicatorConf[network.name]!.felonyJailBlocks, - ]) - ); - await slashProxyContract.deployed(); - slashContract = SlashIndicator__factory.connect(slashProxyContract.address, deployer); - - const stakingVestingLogic = await new StakingVesting__factory(deployer).deploy(); - const stakingVesting = await new TransparentUpgradeableProxy__factory(deployer).deploy( - stakingVestingLogic.address, - proxyAdmin.address, - stakingVestingLogic.interface.encodeFunctionData('initialize', [0, roninValidatorSetAddr]) + await deployments.fixture([ + 'ProxyAdmin', + 'CalculateAddresses', + 'RoninValidatorSetProxy', + 'SlashIndicatorProxy', + 'StakingProxy', + 'StakingVestingProxy', + ]); + + const slashContractDeployment = await deployments.get('SlashIndicatorProxy'); + slashContract = SlashIndicator__factory.connect(slashContractDeployment.address, deployer); + + const stakingContractDeployment = await deployments.get('StakingProxy'); + stakingContract = Staking__factory.connect(stakingContractDeployment.address, deployer); + + const validatorContractDeployment = await deployments.get('RoninValidatorSetProxy'); + validatorContract = MockRoninValidatorSetEpochSetter__factory.connect( + validatorContractDeployment.address, + deployer ); - validatorContract = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy( - governanceAdmin.address, - slashContract.address, - stakingContractAddr, - stakingVesting.address, - maxValidatorNumber - ); - await validatorContract.deployed(); - - const stakingLogicContract = await new Staking__factory(deployer).deploy(); - await stakingLogicContract.deployed(); - - const stakingProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( - stakingLogicContract.address, - proxyAdmin.address, - stakingLogicContract.interface.encodeFunctionData('initialize', [ - validatorContract.address, - governanceAdmin.address, - 100, - minValidatorBalance, - ]) - ); - await stakingProxyContract.deployed(); - stakingContract = Staking__factory.connect(stakingProxyContract.address, deployer); + const mockValidatorLogic = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy(); + await mockValidatorLogic.deployed(); - expect(roninValidatorSetAddr.toLowerCase()).eq(validatorContract.address.toLowerCase()); - expect(stakingContractAddr.toLowerCase()).eq(stakingContract.address.toLowerCase()); + const proxyAdminDeployment = await deployments.get('ProxyAdmin'); + let proxyAdminContract = ProxyAdmin__factory.connect(proxyAdminDeployment.address, deployer); + + await proxyAdminContract.upgrade(validatorContract.address, mockValidatorLogic.address); }); describe('Configuration check', async () => { @@ -176,7 +164,7 @@ describe('[Integration] Submit Block Reward', () => { it('Should the ValidatorSetContract emit event of submitting reward', async () => { await expect(submitRewardTx) .to.emit(validatorContract, 'BlockRewardSubmitted') - .withArgs(validator.address, blockRewardAmount); + .withArgs(validator.address, blockRewardAmount, bonusPerBlock); }); it.skip('Should the ValidatorSetContract update mining reward', async () => {}); diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index 6b6d550fa..ef6867d1d 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { network, ethers } from 'hardhat'; +import { network, ethers, deployments } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { @@ -9,10 +9,16 @@ import { Staking__factory, MockRoninValidatorSetEpochSetter__factory, MockRoninValidatorSetEpochSetter, - TransparentUpgradeableProxy__factory, - StakingVesting__factory, + ProxyAdmin__factory, } from '../../src/types'; -import { Network, slashIndicatorConf, roninValidatorSetConf, stakingConfig, initAddress } from '../../src/config'; +import { + Network, + slashIndicatorConf, + roninValidatorSetConf, + stakingConfig, + initAddress, + stakingVestingConfig, +} from '../../src/config'; import { BigNumber, ContractTransaction } from 'ethers'; import { expects as StakingExpects } from '../helpers/reward-calculation'; import { expects as SlashExpects } from '../helpers/slash-indicator'; @@ -26,21 +32,29 @@ let validatorContract: MockRoninValidatorSetEpochSetter; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; let governanceAdmin: SignerWithAddress; -let proxyAdmin: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; +const felonyJailDuration = 28800 * 2; +const misdemeanorThreshold = 10; +const felonyThreshold = 20; const slashFelonyAmount = BigNumber.from(1); const slashDoubleSignAmount = 1000; + const maxValidatorNumber = 3; +const numberOfBlocksInEpoch = 600; +const numberOfEpochsInPeriod = 48; + const minValidatorBalance = BigNumber.from(100); -const felonyJailDuration = 28800 * 2; -const misdemeanorThreshold = 10; -const felonyThreshold = 20; -const blockRewardAmount = BigNumber.from(2); +const maxValidatorCandidate = 10; + +const bonusPerBlock = BigNumber.from(1); +const topUpAmount = BigNumber.from(10000); describe('[Integration] Wrap up epoch', () => { + const blockRewardAmount = BigNumber.from(2); + before(async () => { - [coinbase, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + [deployer, coinbase, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); if (network.name == Network.Hardhat) { @@ -56,74 +70,47 @@ describe('[Integration] Wrap up epoch', () => { }; roninValidatorSetConf[network.name] = { maxValidatorNumber: maxValidatorNumber, - numberOfBlocksInEpoch: 600, - numberOfEpochsInPeriod: 48, // 1 day + numberOfBlocksInEpoch: numberOfBlocksInEpoch, + numberOfEpochsInPeriod: numberOfEpochsInPeriod, }; stakingConfig[network.name] = { minValidatorBalance: minValidatorBalance, - maxValidatorCandidate: maxValidatorNumber, + maxValidatorCandidate: maxValidatorCandidate, + }; + stakingVestingConfig[network.name] = { + bonusPerBlock: bonusPerBlock, + topupAmount: topUpAmount, }; } - const nonce = await deployer.getTransactionCount(); - const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); - const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 6 }); - - const slashLogicContract = await new SlashIndicator__factory(deployer).deploy(); - await slashLogicContract.deployed(); - - const slashProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( - slashLogicContract.address, - proxyAdmin.address, - slashLogicContract.interface.encodeFunctionData('initialize', [ - governanceAdmin.address, - roninValidatorSetAddr, - slashIndicatorConf[network.name]!.misdemeanorThreshold, - slashIndicatorConf[network.name]!.felonyThreshold, - slashIndicatorConf[network.name]!.slashFelonyAmount, - slashIndicatorConf[network.name]!.slashDoubleSignAmount, - slashIndicatorConf[network.name]!.felonyJailBlocks, - ]) - ); - await slashProxyContract.deployed(); - slashContract = SlashIndicator__factory.connect(slashProxyContract.address, deployer); - - const stakingVestingLogic = await new StakingVesting__factory(deployer).deploy(); - const stakingVesting = await new TransparentUpgradeableProxy__factory(deployer).deploy( - stakingVestingLogic.address, - proxyAdmin.address, - stakingVestingLogic.interface.encodeFunctionData('initialize', [0, roninValidatorSetAddr]) + await deployments.fixture([ + 'ProxyAdmin', + 'CalculateAddresses', + 'RoninValidatorSetProxy', + 'SlashIndicatorProxy', + 'StakingProxy', + 'StakingVestingProxy', + ]); + + const slashContractDeployment = await deployments.get('SlashIndicatorProxy'); + slashContract = SlashIndicator__factory.connect(slashContractDeployment.address, deployer); + + const stakingContractDeployment = await deployments.get('StakingProxy'); + stakingContract = Staking__factory.connect(stakingContractDeployment.address, deployer); + + const validatorContractDeployment = await deployments.get('RoninValidatorSetProxy'); + validatorContract = MockRoninValidatorSetEpochSetter__factory.connect( + validatorContractDeployment.address, + deployer ); - validatorContract = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy( - governanceAdmin.address, - slashContract.address, - stakingContractAddr, - stakingVesting.address, - maxValidatorNumber - ); - await validatorContract.deployed(); - - const stakingLogicContract = await new Staking__factory(deployer).deploy(); - await stakingLogicContract.deployed(); - - const stakingProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( - stakingLogicContract.address, - proxyAdmin.address, - stakingLogicContract.interface.encodeFunctionData('initialize', [ - validatorContract.address, - governanceAdmin.address, - 100, - minValidatorBalance, - ]) - ); - await stakingProxyContract.deployed(); - stakingContract = Staking__factory.connect(stakingProxyContract.address, deployer); + const mockValidatorLogic = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy(); + await mockValidatorLogic.deployed(); - expect(roninValidatorSetAddr.toLowerCase(), 'validator set contract mismatch').eq( - validatorContract.address.toLowerCase() - ); - expect(stakingContractAddr.toLowerCase(), 'staking contract mismatch').eq(stakingContract.address.toLowerCase()); + const proxyAdminDeployment = await deployments.get('ProxyAdmin'); + let proxyAdminContract = ProxyAdmin__factory.connect(proxyAdminDeployment.address, deployer); + + await proxyAdminContract.upgrade(validatorContract.address, mockValidatorLogic.address); }); describe('Configuration test', () => { diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index 01736dfc9..eaadf402c 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -31,9 +31,17 @@ let currentValidatorSet: string[]; const slashFelonyAmount = 100; const slashDoubleSignAmount = 1000; + const maxValidatorNumber = 4; +const numberOfBlocksInEpoch = 600; +const numberOfEpochsInPeriod = 48; + +const maxValidatorCandidate = 100; const minValidatorBalance = BigNumber.from(2); +const bonusPerBlock = BigNumber.from(1); +const topUpAmount = BigNumber.from(10000); + describe('Ronin Validator Set test', () => { before(async () => { [coinbase, treasury, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); @@ -41,16 +49,25 @@ describe('Ronin Validator Set test', () => { await network.provider.send('hardhat_setCoinbase', [coinbase.address]); const nonce = await deployer.getTransactionCount(); - const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 3 }); - const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 5 }); + const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); + const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 6 }); + + /// + /// Deploy staking mock contract + /// const stakingVestingLogic = await new StakingVesting__factory(deployer).deploy(); const stakingVesting = await new TransparentUpgradeableProxy__factory(deployer).deploy( stakingVestingLogic.address, proxyAdmin.address, - stakingVestingLogic.interface.encodeFunctionData('initialize', [0, roninValidatorSetAddr]) + stakingVestingLogic.interface.encodeFunctionData('initialize', [bonusPerBlock, roninValidatorSetAddr]), + { value: topUpAmount } ); + /// + /// Deploy slash indicator contract + /// + slashIndicator = await new MockSlashIndicator__factory(deployer).deploy( roninValidatorSetAddr, slashFelonyAmount, @@ -58,33 +75,55 @@ describe('Ronin Validator Set test', () => { ); await slashIndicator.deployed(); - roninValidatorSet = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy( - governanceAdmin.address, - slashIndicator.address, - stakingContractAddr, - stakingVesting.address, - maxValidatorNumber + /// + /// Deploy validator mock contract + /// + + const validatorLogicContract = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy(); + await validatorLogicContract.deployed(); + + const validatorProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + validatorLogicContract.address, + proxyAdmin.address, + validatorLogicContract.interface.encodeFunctionData('initialize', [ + governanceAdmin.address, + slashIndicator.address, + stakingContractAddr, + stakingVesting.address, + maxValidatorNumber, + numberOfBlocksInEpoch, + numberOfEpochsInPeriod, + ]) ); - await roninValidatorSet.deployed(); + await validatorProxyContract.deployed(); + roninValidatorSet = MockRoninValidatorSetEpochSetter__factory.connect(validatorProxyContract.address, deployer); - const logicContract = await new Staking__factory(deployer).deploy(); - await logicContract.deployed(); + /// + /// Deploy staking contract + /// - const proxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( - logicContract.address, + const stakingLogicContract = await new Staking__factory(deployer).deploy(); + await stakingLogicContract.deployed(); + + const stakingProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + stakingLogicContract.address, proxyAdmin.address, - logicContract.interface.encodeFunctionData('initialize', [ + stakingLogicContract.interface.encodeFunctionData('initialize', [ roninValidatorSet.address, governanceAdmin.address, - 100, + maxValidatorCandidate, minValidatorBalance, ]) ); - await proxyContract.deployed(); - stakingContract = Staking__factory.connect(proxyContract.address, deployer); + await stakingProxyContract.deployed(); + stakingContract = Staking__factory.connect(stakingProxyContract.address, deployer); - expect(roninValidatorSetAddr.toLowerCase()).eq(roninValidatorSet.address.toLowerCase()); - expect(stakingContractAddr.toLowerCase()).eq(stakingContract.address.toLowerCase()); + expect(roninValidatorSetAddr.toLowerCase(), 'wrong ronin validator set contract address').eq( + roninValidatorSet.address.toLowerCase() + ); + expect(stakingContractAddr.toLowerCase(), 'wrong staking contract address').eq( + stakingContract.address.toLowerCase() + ); }); after(async () => { @@ -180,7 +219,7 @@ describe('Ronin Validator Set test', () => { it('Should be able to submit block reward using coinbase account', async () => { const tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); - await RoninValidatorSet.expects.emitBlockRewardSubmittedEvent(tx, coinbase.address, 100); + await RoninValidatorSet.expects.emitBlockRewardSubmittedEvent(tx, coinbase.address, 100, bonusPerBlock); }); it('Should be able to get right reward at the end of period', async () => { @@ -192,11 +231,11 @@ describe('Ronin Validator Set test', () => { tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); - await RoninValidatorSet.expects.emitStakingRewardDistributedEvent(tx!, 99); + await RoninValidatorSet.expects.emitStakingRewardDistributedEvent(tx!, bonusPerBlock.add(99)); await RoninValidatorSet.expects.emitMiningRewardDistributedEvent(tx!, coinbase.address, 1); const balanceDiff = (await treasury.getBalance()).sub(balance); expect(balanceDiff).eq(1); // 100 * 1% - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(99); // remain amount (99%) + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(bonusPerBlock.add(99)); // remain amount (99%) }); it('Should not allocate minting fee for the slashed validators', async () => { @@ -214,7 +253,7 @@ describe('Ronin Validator Set test', () => { const balanceDiff = (await treasury.getBalance()).sub(balance); expect(balanceDiff).eq(0); // The delegators don't receives the new rewards until the period is ended - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(99); + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(bonusPerBlock.add(99)); await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); } @@ -228,7 +267,7 @@ describe('Ronin Validator Set test', () => { }); const balanceDiff = (await treasury.getBalance()).sub(balance); expect(balanceDiff).eq(0); - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(99); + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(bonusPerBlock.add(99)); await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); } }); @@ -243,9 +282,11 @@ describe('Ronin Validator Set test', () => { }); const balanceDiff = (await treasury.getBalance()).sub(balance); expect(balanceDiff).eq(0); - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(99 * 2); + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + bonusPerBlock.add(99).mul(2) + ); await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); - await RoninValidatorSet.expects.emitStakingRewardDistributedEvent(tx!, 99); + await RoninValidatorSet.expects.emitStakingRewardDistributedEvent(tx!, bonusPerBlock.add(99)); }); it('Should not allocate reward for the slashed validator', async () => { @@ -261,7 +302,9 @@ describe('Ronin Validator Set test', () => { }); const balanceDiff = (await treasury.getBalance()).sub(balance); expect(balanceDiff).eq(0); - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(99 * 2); + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + bonusPerBlock.add(99).mul(2) + ); await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); }); }); From 28e71aad9d3b2d85fbfc192d04b0c0c740850882 Mon Sep 17 00:00:00 2001 From: Bao Date: Thu, 15 Sep 2022 11:52:41 +0700 Subject: [PATCH 136/190] Validator set contract excludes under balance validators in updating set (#11) * [ValidatorSet] Exclude validators that under balance in updating validator set * [IntegrationTest] Update test of under balance validators --- contracts/staking/Staking.sol | 11 +++++++++++ test/integration/ActionSlashValidators.test.ts | 10 ++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 8bed21782..313bd9877 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -132,13 +132,24 @@ contract Staking is IStaking, StakingManager, Initializable { */ function getCandidateWeights() external view returns (address[] memory _candidates, uint256[] memory _weights) { uint256 _length = validatorCandidates.length; + uint256 _newLength = _length; _candidates = new address[](_length); _weights = new uint256[](_length); + for (uint256 _i; _i < _length; _i++) { ValidatorCandidate storage _candidate = validatorCandidates[_i]; + if (_candidate.stakedAmount < _minValidatorBalance) { + _newLength--; + continue; + } _candidates[_i] = _candidate.consensusAddr; _weights[_i] = _candidate.delegatedAmount; } + + assembly { + mstore(_candidates, _newLength) + mstore(_weights, _newLength) + } } /** diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index f385b3638..e06a4bd8c 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -356,7 +356,7 @@ describe('[Integration] Slash validators', () => { await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); }); - it('Should the jail time end', async () => { + it('Should the validator candidate cannot join as a validator when jail time is over, due to insufficient fund', async () => { let _blockNumber = await network.provider.send('eth_blockNumber'); let _jailUntil = await validatorContract.getJailUntils([slashee.address]); let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber); @@ -366,13 +366,6 @@ describe('[Integration] Slash validators', () => { await validatorContract.connect(coinbase).endEpoch(); updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); - // FIXME: Fix this assertion when update contract to check minimum amount on updating validator set - expectingValidatorSet.push(slashee.address); - }); - - it.skip('Should the validator candidate cannot join as a validator when jail time is over, due to insufficient fund', async () => { - // FIXME: Fix this assertion when update contract to check minimum amount on updating validator set - // expectingValidatorSet.pop(); await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); }); @@ -392,6 +385,7 @@ describe('[Integration] Slash validators', () => { await validatorContract.connect(coinbase).endEpoch(); updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); + expectingValidatorSet.push(slashee.address); await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); }); }); From f9a0b3e4226afd2e46c6f12ed6739cdefb2600b6 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Thu, 15 Sep 2022 14:14:56 +0700 Subject: [PATCH 137/190] Refine structure & optimize gas cost (#12) * Separate the method `updateValidatorSet` * Optimize gas cost * Refine test structures --- .../MockRoninValidatorSetEpochSetter.sol | 14 +++--- .../ronin-validator/RoninValidatorSet.sol | 45 ++++++++++--------- contracts/staking/RewardCalculation.sol | 5 +-- src/config.ts | 4 +- src/deploy/proxy/staking-vesting-proxy.ts | 3 +- .../script/slash-indicator.ts | 0 test/helpers/utils.ts | 8 ++++ .../integration/ActionSlashValidators.test.ts | 6 +-- test/integration/ActionSubmitReward.test.ts | 2 +- test/integration/ActionWrapUpEpoch.test.ts | 2 +- test/slash/SlashIndicator.test.ts | 2 +- test/utils.ts | 8 ---- test/validator/RoninValidatorSet.test.ts | 2 +- 13 files changed, 53 insertions(+), 48 deletions(-) rename test/slash/slashType.ts => src/script/slash-indicator.ts (100%) delete mode 100644 test/utils.ts diff --git a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol index 03a8f6c12..352a917c7 100644 --- a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol +++ b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol @@ -8,7 +8,7 @@ contract MockRoninValidatorSetEpochSetter is RoninValidatorSet { uint256[] internal _epochs; uint256[] internal _periods; - constructor() RoninValidatorSet() {} + constructor() {} function endEpoch() external { _epochs.push(block.number); @@ -19,17 +19,17 @@ contract MockRoninValidatorSetEpochSetter is RoninValidatorSet { } function periodOf(uint256 _block) public view override returns (uint256 _period) { - for (uint256 _i; _i < _periods.length; _i++) { - if (_block >= _periods[_i]) { - _period = _i + 1; + for (uint256 _i = _periods.length; _i > 0; _i--) { + if (_block >= _periods[_i - 1]) { + return _i; } } } function epochOf(uint256 _block) public view override returns (uint256 _epoch) { - for (uint256 _i; _i < _epochs.length; _i++) { - if (_block >= _epochs[_i]) { - _epoch = _i + 1; + for (uint256 _i = _epochs.length; _i > 0; _i--) { + if (_block >= _epochs[_i - 1]) { + return _i; } } } diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index 1c47ef7f9..98efb3767 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -36,8 +36,6 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { uint256 internal _numberOfEpochsInPeriod; /// @dev The last updated block uint256 internal _lastUpdatedBlock; - /// @dev Mapping from epoch index => flag indicating the epoch is wrapped up or not - mapping(uint256 => bool) internal _wrappedUp; /// @dev Mapping from validator address => the last period that the validator has no pending reward mapping(address => mapping(uint256 => bool)) internal _rewardDeprecatedAtPeriod; @@ -143,10 +141,11 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { /** * @inheritdoc IRoninValidatorSet */ - function wrapUpEpoch() external payable override onlyCoinbase whenEpochEnding { - uint256 _epoch = epochOf(block.number); - require(_wrappedUp[_epoch] == false, "RoninValidatorSet: query for already wrapped up epoch"); - _wrappedUp[_epoch] = true; + function wrapUpEpoch() external payable virtual override onlyCoinbase whenEpochEnding { + require( + epochOf(_lastUpdatedBlock) < epochOf(block.number), + "RoninValidatorSet: query for already wrapped up epoch" + ); IStaking _staking = IStaking(_stakingContract); address _validatorAddr; @@ -400,17 +399,12 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { } /** - * @dev Updates the validator set based on the validator candidates from the Staking contract. - * - * Emits the `ValidatorSetUpdated` event. - * + * @dev Returns validator candidates list. */ - function _updateValidatorSet() internal { - // TODO: measure gas metrics from getting candidates - + function _getValidatorCandidates() internal view returns (address[] memory _candidates) { + uint256[] memory _weights; + (_candidates, _weights) = IStaking(_stakingContract).getCandidateWeights(); // TODO: filter validators that do not have enough min balance - - (address[] memory _candidates, uint256[] memory _weights) = IStaking(_stakingContract).getCandidateWeights(); uint256 _newLength = _candidates.length; for (uint256 _i; _i < _candidates.length; _i++) { if (_jailed(_candidates[_i])) { @@ -426,24 +420,35 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { } _candidates = Sorting.sort(_candidates, _weights); - // TODO: measure gas metrics to after sorting candidates + // TODO: pick at least M governers as validators + } + + /** + * @dev Updates the validator set based on the validator candidates from the Staking contract. + * + * Emits the `ValidatorSetUpdated` event. + * + */ + function _updateValidatorSet() internal virtual { + address[] memory _candidates = _getValidatorCandidates(); + uint256 _newValidatorCount = Math.min(_maxValidatorNumber, _candidates.length); assembly { mstore(_candidates, _newValidatorCount) } - // TODO: pick at least M governers as validators - for (uint256 _i = _newValidatorCount; _i < validatorCount; _i++) { delete _validator[_i]; delete _validatorMap[_validator[_i]]; } for (uint256 _i = 0; _i < _newValidatorCount; _i++) { - delete _validatorMap[_validator[_i]]; - address _newValidator = _candidates[_i]; + if (_newValidator == _validator[_i]) { + continue; + } + delete _validatorMap[_validator[_i]]; _validatorMap[_newValidator] = true; _validator[_i] = _newValidator; } diff --git a/contracts/staking/RewardCalculation.sol b/contracts/staking/RewardCalculation.sol index 7875f2bd2..6dfd8f862 100644 --- a/contracts/staking/RewardCalculation.sol +++ b/contracts/staking/RewardCalculation.sol @@ -181,13 +181,12 @@ abstract contract RewardCalculation is IRewardPool { function _onPoolsSettled(address[] calldata _poolList) internal { uint256[] memory _accumulatedRpsList = new uint256[](_poolList.length); address _poolAddr; - uint256 _accumulatedRps; for (uint256 _i; _i < _poolList.length; _i++) { _poolAddr = _poolList[_i]; - _accumulatedRps = _accumulatedRpsList[_i] = _pendingPool[_poolAddr].accumulatedRps; + _accumulatedRpsList[_i] = _pendingPool[_poolAddr].accumulatedRps; SettledPool storage _sPool = _settledPool[_poolAddr]; - _sPool.accumulatedRps = _accumulatedRps; + _sPool.accumulatedRps = _accumulatedRpsList[_i]; _sPool.lastSyncedBlock = block.number; } emit SettledPoolsUpdated(_poolList, _accumulatedRpsList); diff --git a/src/config.ts b/src/config.ts index be0a6b40d..d93951dd6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -34,8 +34,8 @@ export interface StakingConf { export interface StakingVestingConf { [network: LiteralNetwork]: | { - bonusPerBlock: BigNumber; - topupAmount: BigNumber; + bonusPerBlock: BigNumberish; + topupAmount: BigNumberish; } | undefined; } diff --git a/src/deploy/proxy/staking-vesting-proxy.ts b/src/deploy/proxy/staking-vesting-proxy.ts index 3c3f99e88..34b8f9f05 100644 --- a/src/deploy/proxy/staking-vesting-proxy.ts +++ b/src/deploy/proxy/staking-vesting-proxy.ts @@ -1,3 +1,4 @@ +import { BigNumber } from 'ethers'; import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { initAddress, stakingVestingConfig } from '../../config'; @@ -20,7 +21,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme from: deployer, log: true, args: [logicContract.address, proxyAdmin.address, data], - value: stakingVestingConfig[network.name]!.topupAmount, + value: BigNumber.from(stakingVestingConfig[network.name]!.topupAmount), }); }; diff --git a/test/slash/slashType.ts b/src/script/slash-indicator.ts similarity index 100% rename from test/slash/slashType.ts rename to src/script/slash-indicator.ts diff --git a/test/helpers/utils.ts b/test/helpers/utils.ts index 54df11a04..2db4cb7ef 100644 --- a/test/helpers/utils.ts +++ b/test/helpers/utils.ts @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { ContractTransaction } from 'ethers'; import { Interface, LogDescription } from 'ethers/lib/utils'; +import { network } from 'hardhat'; export const expectEvent = async ( contractInterface: Interface, @@ -24,3 +25,10 @@ export const expectEvent = async ( expect(counter, 'invalid number of emitted events').eq(eventNumbers); }; + +export const mineBatchTxs = async (fn: () => Promise) => { + await network.provider.send('evm_setAutomine', [false]); + await fn(); + await network.provider.send('evm_mine'); + await network.provider.send('evm_setAutomine', [true]); +}; diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index e06a4bd8c..b4d65f4bf 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { network, ethers, deployments, getNamedAccounts } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { Address } from 'hardhat-deploy/dist/types'; import { SlashIndicator, @@ -20,10 +21,9 @@ import { initAddress, } from '../../src/config'; import { BigNumber, ContractTransaction } from 'ethers'; -import { SlashType } from '../slash/slashType'; import { expects as RoninValidatorSetExpects } from '../helpers/ronin-validator-set'; -import { mineBatchTxs } from '../utils'; -import { Address } from 'hardhat-deploy/dist/types'; +import { mineBatchTxs } from '../helpers/utils'; +import { SlashType } from '../../src/script/slash-indicator'; let slashContract: SlashIndicator; let stakingContract: Staking; diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index 455fef6e4..8e5fd43f9 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -21,7 +21,7 @@ import { stakingVestingConfig, } from '../../src/config'; import { BigNumber, ContractTransaction } from 'ethers'; -import { mineBatchTxs } from '../utils'; +import { mineBatchTxs } from '../helpers/utils'; let slashContract: SlashIndicator; let stakingContract: Staking; diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index ef6867d1d..3468ca891 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -23,7 +23,7 @@ import { BigNumber, ContractTransaction } from 'ethers'; import { expects as StakingExpects } from '../helpers/reward-calculation'; import { expects as SlashExpects } from '../helpers/slash-indicator'; import { expects as ValidatorSetExpects } from '../helpers/ronin-validator-set'; -import { mineBatchTxs } from '../utils'; +import { mineBatchTxs } from '../helpers/utils'; let slashContract: SlashIndicator; let stakingContract: Staking; diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index 793116ae5..2ee1ab51b 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -10,7 +10,7 @@ import { TransparentUpgradeableProxy__factory, } from '../../src/types'; import { Address } from 'hardhat-deploy/dist/types'; -import { SlashType } from './slashType'; +import { SlashType } from '../../src/script/slash-indicator'; import { Network, slashIndicatorConf } from '../../src/config'; import { BigNumber, Signer } from 'ethers'; import { expects as SlashExpects } from '../helpers/slash-indicator'; diff --git a/test/utils.ts b/test/utils.ts deleted file mode 100644 index 286e93b14..000000000 --- a/test/utils.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { network } from "hardhat"; - -export const mineBatchTxs = async (fn: () => Promise) => { - await network.provider.send('evm_setAutomine', [false]); - await fn(); - await network.provider.send('evm_mine'); - await network.provider.send('evm_setAutomine', [true]); -}; diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index eaadf402c..af7bd201e 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -14,7 +14,7 @@ import { StakingVesting__factory, } from '../../src/types'; import * as RoninValidatorSet from '../helpers/ronin-validator-set'; -import { mineBatchTxs } from '../utils'; +import { mineBatchTxs } from '../helpers/utils'; let roninValidatorSet: MockRoninValidatorSetEpochSetter; let stakingContract: Staking; From 4bee32f6556422d4bf68acb0a39ecbf76747bba3 Mon Sep 17 00:00:00 2001 From: Bao Date: Thu, 15 Sep 2022 17:00:30 +0700 Subject: [PATCH 138/190] Fix getter of governanceAdmin (#13) --- contracts/ronin-validator/RoninValidatorSet.sol | 2 +- contracts/slashing/SlashIndicator.sol | 2 +- test/integration/Configuration.test.ts | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/ronin-validator/RoninValidatorSet.sol index 98efb3767..803975f01 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/ronin-validator/RoninValidatorSet.sol @@ -468,7 +468,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { require(__governanceAdmin != address(0), "RoninValidatorSet: Cannot set admin to zero address"); - _governanceAdmin == __governanceAdmin; + _governanceAdmin = __governanceAdmin; emit GovernanceAdminUpdated(__governanceAdmin); } diff --git a/contracts/slashing/SlashIndicator.sol b/contracts/slashing/SlashIndicator.sol index d50000d5d..c69ac52ec 100644 --- a/contracts/slashing/SlashIndicator.sol +++ b/contracts/slashing/SlashIndicator.sol @@ -217,7 +217,7 @@ contract SlashIndicator is ISlashIndicator, Initializable { require(__governanceAdmin != address(0), "SlashIndicator: Cannot set admin to zero address"); - _governanceAdmin == __governanceAdmin; + _governanceAdmin = __governanceAdmin; emit GovernanceAdminUpdated(__governanceAdmin); } diff --git a/test/integration/Configuration.test.ts b/test/integration/Configuration.test.ts index 071eceab9..e242596a2 100644 --- a/test/integration/Configuration.test.ts +++ b/test/integration/Configuration.test.ts @@ -85,6 +85,11 @@ describe('[Integration] Configuration check', () => { }); describe('ValidatorSetContract configuration', async () => { + it('Should config the governanceAdmin correctly', async () => { + let _governanceAdmin = await validatorContract.governanceAdmin(); + expect(_governanceAdmin).to.eq(governanceAdmin.address); + }); + it('Should the ValidatorSetContract config the StakingContract correctly', async () => { let _stakingContract = await validatorContract.stakingContract(); expect(_stakingContract).to.eq(stakingContract.address); @@ -112,6 +117,11 @@ describe('[Integration] Configuration check', () => { }); describe('StakingContract configuration', async () => { + it('Should config the governanceAdmin correctly', async () => { + let _governanceAdmin = await stakingContract.governanceAdmin(); + expect(_governanceAdmin).to.eq(governanceAdmin.address); + }); + it('Should the StakingContract config the ValidatorSetContract correctly', async () => { let _validatorSetContract = await stakingContract.validatorContract(); expect(_validatorSetContract).to.eq(validatorContract.address); @@ -129,6 +139,11 @@ describe('[Integration] Configuration check', () => { }); describe('SlashIndicatorContract configuration', async () => { + it('Should config the governanceAdmin correctly', async () => { + let _governanceAdmin = await slashContract.governanceAdmin(); + expect(_governanceAdmin).to.eq(governanceAdmin.address); + }); + it('Should the SlashIndicatorContract config the ValidatorSetContract correctly', async () => { let _validatorSetContract = await slashContract.validatorContract(); expect(_validatorSetContract).to.eq(validatorContract.address); From 7bedd71adde0c13bca22f6a2393f4d935d23593e Mon Sep 17 00:00:00 2001 From: Ngo Nguyen Vu Huy Date: Thu, 15 Sep 2022 17:22:40 +0700 Subject: [PATCH 139/190] Add github action to dpos (#14) * Add github action to dpos * Update version node * update branch Co-authored-by: NGO NGUYEN VU HUY --- .github/workflows/unittest.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/unittest.yml diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml new file mode 100644 index 000000000..6bc1b019a --- /dev/null +++ b/.github/workflows/unittest.yml @@ -0,0 +1,14 @@ +name: Run tests +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + uses: axieinfinity/workflows-samples/.github/workflows/testing-node.yml@main + with: + node_version: v14.18.1 \ No newline at end of file From c4f6bd4eafc994ff4ccd6cb661004c4d33a65892 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Mon, 19 Sep 2022 11:27:04 +0700 Subject: [PATCH 140/190] Update emitted arguments for the event `SettledRewardUpdated` (#16) Update emitted arguments for the event `SettledRewardUpdated` --- contracts/staking/RewardCalculation.sol | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/contracts/staking/RewardCalculation.sol b/contracts/staking/RewardCalculation.sol index 6dfd8f862..fc80c1789 100644 --- a/contracts/staking/RewardCalculation.sol +++ b/contracts/staking/RewardCalculation.sol @@ -7,9 +7,6 @@ import "../interfaces/IRewardPool.sol"; /** * @title RewardCalculation contract * @dev This contract mainly contains to calculate reward for staking contract. - * - * TODO(Thor): optimize gas cost when emitting SettledRewardUpdated and PendingRewardUpdated in the method `_claimReward`; - * */ abstract contract RewardCalculation is IRewardPool { /// @dev Mapping from the pool address => user address => settled reward info of the user @@ -133,7 +130,7 @@ abstract contract RewardCalculation is IRewardPool { _sReward.balance = balanceOf(_poolAddr, _user); _sReward.accumulatedRps = _sPool.accumulatedRps; } - emit SettledRewardUpdated(_poolAddr, _user, _sReward.balance, 0, _sPool.accumulatedRps); + emit SettledRewardUpdated(_poolAddr, _user, _sReward.balance, 0, _sReward.accumulatedRps); _reward.credited += _amount; _reward.lastSyncedBlock = block.number; From ebf5785f6f5cff21a1c52568660d76c45eb9aff7 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 20 Sep 2022 11:38:38 +0700 Subject: [PATCH 141/190] Refine structures & fix TODO in Staking contract --- .../RoninValidatorSet.sol | 12 +++--- contracts/{slashing => }/SlashIndicator.sol | 4 +- contracts/extensions/RONTransferHelper.sol | 26 +++++++++++ contracts/interfaces/IStaking.sol | 4 +- .../MockRoninValidatorSetEpochSetter.sol | 2 +- contracts/staking/Staking.sol | 3 +- contracts/staking/StakingManager.sol | 43 ++++++++----------- test/sorting/MockSwapStorage.test.ts | 2 +- 8 files changed, 57 insertions(+), 39 deletions(-) rename contracts/{ronin-validator => }/RoninValidatorSet.sol (98%) rename contracts/{slashing => }/SlashIndicator.sol (98%) create mode 100644 contracts/extensions/RONTransferHelper.sol diff --git a/contracts/ronin-validator/RoninValidatorSet.sol b/contracts/RoninValidatorSet.sol similarity index 98% rename from contracts/ronin-validator/RoninValidatorSet.sol rename to contracts/RoninValidatorSet.sol index 803975f01..9dae36449 100644 --- a/contracts/ronin-validator/RoninValidatorSet.sol +++ b/contracts/RoninValidatorSet.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import "../interfaces/ISlashIndicator.sol"; -import "../interfaces/IStaking.sol"; -import "../interfaces/IStakingVesting.sol"; -import "../interfaces/IRoninValidatorSet.sol"; -import "../libraries/Sorting.sol"; -import "../libraries/Math.sol"; +import "./interfaces/ISlashIndicator.sol"; +import "./interfaces/IStaking.sol"; +import "./interfaces/IStakingVesting.sol"; +import "./interfaces/IRoninValidatorSet.sol"; +import "./libraries/Sorting.sol"; +import "./libraries/Math.sol"; contract RoninValidatorSet is IRoninValidatorSet, Initializable { /// @dev Governance admin address. diff --git a/contracts/slashing/SlashIndicator.sol b/contracts/SlashIndicator.sol similarity index 98% rename from contracts/slashing/SlashIndicator.sol rename to contracts/SlashIndicator.sol index c69ac52ec..d21b948cb 100644 --- a/contracts/slashing/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "../interfaces/ISlashIndicator.sol"; -import "../interfaces/IRoninValidatorSet.sol"; +import "./interfaces/ISlashIndicator.sol"; +import "./interfaces/IRoninValidatorSet.sol"; contract SlashIndicator is ISlashIndicator, Initializable { /// @dev Mapping from validator address => unavailability indicator diff --git a/contracts/extensions/RONTransferHelper.sol b/contracts/extensions/RONTransferHelper.sol new file mode 100644 index 000000000..75e64508a --- /dev/null +++ b/contracts/extensions/RONTransferHelper.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +abstract contract RONTransferHelper { + /** + * @dev Send `_amount` RON to the address `_recipient`. + * Returns whether the recipient receives RON or not. + * Reverts once the contract balance is insufficient. + * + * Note: consider using `ReentrancyGuard` before calling this function. + * + */ + function _sendRON(address payable _recipient, uint256 _amount) internal returns (bool _success) { + require(address(this).balance >= _amount, "RONTransfer: insufficient balance"); + (_success, ) = _recipient.call{ value: _amount }(""); + } + + /** + * @dev See `_sendRON`. + * Reverts if the recipient does not receive RON. + */ + function _transferRON(address payable _recipient, uint256 _amount) internal { + require(_sendRON(_recipient, _amount), "RONTransfer: unable to transfer value, recipient may have reverted"); + } +} diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index fada079f3..c4f424dbb 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -30,8 +30,8 @@ interface IStaking is IRewardPool { bool governing; /// @dev State of the validator ValidatorState state; - /// @dev For upgrability purpose - uint256[20] ____gap; + /// @dev Extra data + bytes extraData; } /// @dev Emitted when the validator candidate is proposed. diff --git a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol index 352a917c7..ff922eca9 100644 --- a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol +++ b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.9; -import "../ronin-validator/RoninValidatorSet.sol"; +import "../RoninValidatorSet.sol"; contract MockRoninValidatorSetEpochSetter is RoninValidatorSet { uint256[] internal _epochs; diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 313bd9877..0076de0da 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.9; -// import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "../interfaces/IStaking.sol"; import "../interfaces/IRoninValidatorSet.sol"; @@ -172,7 +171,7 @@ contract Staking is IStaking, StakingManager, Initializable { /** * @inheritdoc IStaking */ - function sinkPendingReward(address _consensusAddr) external { + function sinkPendingReward(address _consensusAddr) external onlyValidatorContract { uint256 _period = _periodOf(block.number); _pRewardSinked[_consensusAddr][_period] = true; _sinkPendingReward(_consensusAddr); diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index ad068f8cd..2b656dc65 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -2,10 +2,12 @@ pragma solidity ^0.8.9; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import "../extensions/RONTransferHelper.sol"; import "../interfaces/IStaking.sol"; import "./RewardCalculation.sol"; -abstract contract StakingManager is IStaking, RewardCalculation { +abstract contract StakingManager is IStaking, RONTransferHelper, ReentrancyGuard, RewardCalculation { /// @dev Mapping from pool address => delegator address => delegated amount. mapping(address => mapping(address => uint256)) internal _delegatedAmount; @@ -66,11 +68,11 @@ abstract contract StakingManager is IStaking, RewardCalculation { address _consensusAddr, address payable _treasuryAddr, uint256 _commissionRate - ) external payable override returns (uint256 _candidateIdx) { + ) external payable override nonReentrant returns (uint256 _candidateIdx) { uint256 _amount = msg.value; - address _stakingAddr = msg.sender; - _candidateIdx = _proposeValidator(_consensusAddr, _treasuryAddr, _commissionRate, _amount, _stakingAddr); - _stake(_consensusAddr, _stakingAddr, _amount); + address payable _candidateAdmin = payable(msg.sender); + _candidateIdx = _proposeValidator(_candidateAdmin, _consensusAddr, _treasuryAddr, _commissionRate, _amount); + _stake(_consensusAddr, _candidateAdmin, _amount); } /** @@ -83,18 +85,15 @@ abstract contract StakingManager is IStaking, RewardCalculation { /** * @inheritdoc IStaking */ - function unstake(address _consensusAddr, uint256 _amount) external override { + function unstake(address _consensusAddr, uint256 _amount) external override nonReentrant { address _delegator = msg.sender; - ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); uint256 remainAmount = _candidate.stakedAmount - _amount; require(remainAmount >= minValidatorBalance(), "StakingManager: invalid staked amount left"); _unstake(_candidate, _delegator, _amount); - - // TODO(Thor): replace by `call` and use reentrancy gruard - require(payable(msg.sender).send(_amount), "StakingManager: could not transfer RON"); + require(_sendRON(payable(_delegator), _amount), "StakingManager: could not transfer RON"); } /** @@ -121,18 +120,18 @@ abstract contract StakingManager is IStaking, RewardCalculation { * */ function _proposeValidator( + address payable _candidateAdmin, address _consensusAddr, address payable _treasuryAddr, uint256 _commissionRate, - uint256 _amount, - address _candidateAdmin + uint256 _amount ) internal returns (uint256 _candidateIdx) { + require(_sendRON(_candidateAdmin, 0), "StakingManager: candidate admin cannot receive RON"); + require(_sendRON(_treasuryAddr, 0), "StakingManager: treasury cannot receive RON"); uint256 _length = getValidatorCandidateLength(); require(_length < maxValidatorCandidate(), "StakingManager: exceeds maximum number of candidates"); require(_getCandidateIndex(_consensusAddr) == 0, "StakingManager: query for existed candidate"); require(_amount >= minValidatorBalance(), "StakingManager: insufficient amount"); - // TODO(Thor): replace by `call` and use reentrancy gruard - require(_treasuryAddr.send(0), "StakingManager: invalid treasury address"); _candidateIdx = ~_length; _setCandidateIndex(_consensusAddr, _candidateIdx); @@ -200,11 +199,10 @@ abstract contract StakingManager is IStaking, RewardCalculation { /** * @inheritdoc IStaking */ - function undelegate(address _consensusAddr, uint256 _amount) external notCandidateAdmin(_consensusAddr) { + function undelegate(address _consensusAddr, uint256 _amount) external notCandidateAdmin(_consensusAddr) nonReentrant { address payable _delegator = payable(msg.sender); _undelegate(_consensusAddr, _delegator, _amount); - // TODO(Thor): replace by `call` and use reentrancy gruard - require(_delegator.send(_amount), "StakingManager: could not transfer RON"); + require(_sendRON(_delegator, _amount), "StakingManager: could not transfer RON"); } /** @@ -242,10 +240,9 @@ abstract contract StakingManager is IStaking, RewardCalculation { /** * @inheritdoc IStaking */ - function claimRewards(address[] calldata _consensusAddrList) external returns (uint256 _amount) { + function claimRewards(address[] calldata _consensusAddrList) external nonReentrant returns (uint256 _amount) { _amount = _claimRewards(msg.sender, _consensusAddrList); - // TODO(Thor): replace by `call` and use reentrancy gruard - require(payable(msg.sender).send(_amount), "StakingManager: could not transfer RON"); + require(_sendRON(payable(msg.sender), _amount), "StakingManager: could not transfer RON"); } /** @@ -314,11 +311,7 @@ abstract contract StakingManager is IStaking, RewardCalculation { /** * @dev Claims rewards from the pools `_poolAddrList`. - * - *@notice This function does not transfer reward to user. - * - * TODO: Check whether pool addr is in the candidate list. or add test for this fn. - * + * Note: This function does not transfer reward to user. */ function _claimRewards(address _user, address[] calldata _poolAddrList) internal returns (uint256 _amount) { for (uint256 _i = 0; _i < _poolAddrList.length; _i++) { diff --git a/test/sorting/MockSwapStorage.test.ts b/test/sorting/MockSwapStorage.test.ts index fd009b3e8..adc28196e 100644 --- a/test/sorting/MockSwapStorage.test.ts +++ b/test/sorting/MockSwapStorage.test.ts @@ -25,7 +25,7 @@ const generateCandidate = ( delegatedAmount: 0, governing: false, state: 0, - ____gap: Array.apply(null, Array(20)).map((_) => 0), + extraData: '0x', }; }; From bcfaa97895c5fadd9e4e4b85f7ed953c836fd2ab Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 20 Sep 2022 11:45:59 +0700 Subject: [PATCH 142/190] Adopt RON transfer helper for Ronin Validator set --- contracts/RoninValidatorSet.sol | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/contracts/RoninValidatorSet.sol b/contracts/RoninValidatorSet.sol index 9dae36449..9ca7ce253 100644 --- a/contracts/RoninValidatorSet.sol +++ b/contracts/RoninValidatorSet.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import "./extensions/RONTransferHelper.sol"; import "./interfaces/ISlashIndicator.sol"; import "./interfaces/IStaking.sol"; import "./interfaces/IStakingVesting.sol"; @@ -11,7 +12,7 @@ import "./interfaces/IRoninValidatorSet.sol"; import "./libraries/Sorting.sol"; import "./libraries/Math.sol"; -contract RoninValidatorSet is IRoninValidatorSet, Initializable { +contract RoninValidatorSet is IRoninValidatorSet, RONTransferHelper, Initializable { /// @dev Governance admin address. address internal _governanceAdmin; /// @dev Slash indicator contract address. @@ -146,6 +147,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { epochOf(_lastUpdatedBlock) < epochOf(block.number), "RoninValidatorSet: query for already wrapped up epoch" ); + _lastUpdatedBlock = block.number; IStaking _staking = IStaking(_stakingContract); address _validatorAddr; @@ -165,9 +167,8 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { uint256 _miningAmount = _miningReward[_validatorAddr]; delete _miningReward[_validatorAddr]; if (_miningAmount > 0) { - address _treasury = _staking.treasuryAddressOf(_validatorAddr); - (bool _success, ) = _treasury.call{ value: _miningAmount }(""); - require(_success, "RoninValidatorSet: could not transfer RON treasury addr"); + address payable _treasury = payable(_staking.treasuryAddressOf(_validatorAddr)); + require(_sendRON(_treasury, _miningAmount), "RoninValidatorSet: could not transfer RON treasury address"); emit MiningRewardDistributed(_validatorAddr, _miningAmount); } } @@ -183,8 +184,7 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { _staking.settleRewardPools(_validators); if (_delegatingAmount > 0) { - (bool _success, ) = address(_staking).call{ value: _delegatingAmount }(""); - require(_success, "RoninValidatorSet: could not transfer RON to staking contract"); + require(_sendRON(payable(address(_staking)), 0), "RoninValidatorSet: could not transfer RON to staking contract"); emit StakingRewardDistributed(_delegatingAmount); } @@ -454,7 +454,6 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { } validatorCount = _newValidatorCount; - _lastUpdatedBlock = block.number; emit ValidatorSetUpdated(_candidates); } @@ -500,6 +499,9 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable { * @dev Only receives RON from staking vesting contract. */ function _fallback() internal view { - require(msg.sender == _stakingVestingContract, "RoninValidatorSet: method caller must be staking vesting contract"); + require( + msg.sender == _stakingVestingContract, + "RoninValidatorSet: only receives RON from staking vesting contract" + ); } } From 8283dd9be32b0f0533434fcddd62b42d609f3648 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 20 Sep 2022 18:28:23 +0700 Subject: [PATCH 143/190] Add Has contracts --- contracts/extensions/HasProxyAdmin.sol | 21 +++++++++ .../extensions/HasSlashIndicatorContract.sol | 46 +++++++++++++++++++ contracts/extensions/HasStakingContract.sol | 43 +++++++++++++++++ .../extensions/HasStakingVestingContract.sol | 46 +++++++++++++++++++ contracts/extensions/HasValidatorContract.sol | 43 +++++++++++++++++ .../IHasSlashIndicatorContract.sol | 25 ++++++++++ .../collections/IHasStakingContract.sol | 25 ++++++++++ .../IHasStakingVestingContract.sol | 25 ++++++++++ .../collections/IHasValidatorContract.sol | 25 ++++++++++ 9 files changed, 299 insertions(+) create mode 100644 contracts/extensions/HasProxyAdmin.sol create mode 100644 contracts/extensions/HasSlashIndicatorContract.sol create mode 100644 contracts/extensions/HasStakingContract.sol create mode 100644 contracts/extensions/HasStakingVestingContract.sol create mode 100644 contracts/extensions/HasValidatorContract.sol create mode 100644 contracts/interfaces/collections/IHasSlashIndicatorContract.sol create mode 100644 contracts/interfaces/collections/IHasStakingContract.sol create mode 100644 contracts/interfaces/collections/IHasStakingVestingContract.sol create mode 100644 contracts/interfaces/collections/IHasValidatorContract.sol diff --git a/contracts/extensions/HasProxyAdmin.sol b/contracts/extensions/HasProxyAdmin.sol new file mode 100644 index 000000000..4c8c30509 --- /dev/null +++ b/contracts/extensions/HasProxyAdmin.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/StorageSlot.sol"; + +abstract contract HasProxyAdmin { + // bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1)); + bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + modifier onlyAdmin() { + require(msg.sender == _getAdmin(), "HasProxyAdmin: unauthorized sender"); + _; + } + + /** + * @dev Returns proxy admin. + */ + function _getAdmin() internal view returns (address) { + return StorageSlot.getAddressSlot(_ADMIN_SLOT).value; + } +} diff --git a/contracts/extensions/HasSlashIndicatorContract.sol b/contracts/extensions/HasSlashIndicatorContract.sol new file mode 100644 index 000000000..6700135b7 --- /dev/null +++ b/contracts/extensions/HasSlashIndicatorContract.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./HasProxyAdmin.sol"; +import "../interfaces/collections/IHasSlashIndicatorContract.sol"; +import "../interfaces/ISlashIndicator.sol"; + +contract HasSlashIndicatorContract is IHasSlashIndicatorContract, HasProxyAdmin { + ISlashIndicator internal _slashIndicatorContract; + + modifier onlySlashIndicatorContract() { + require( + slashIndicatorContract() == msg.sender, + "HasSlashIndicatorContract: method caller must be slash indicator contract" + ); + _; + } + + /** + * @inheritdoc IHasSlashIndicatorContract + */ + function slashIndicatorContract() public view override returns (address) { + return address(_slashIndicatorContract); + } + + /** + * @inheritdoc IHasSlashIndicatorContract + */ + function setSlashIndicatorContract(address _addr) external override onlyAdmin { + _setSlashIndicatorContract(_addr); + } + + /** + * @dev Sets the slash indicator contract. + * + * Requirements: + * - The new address is a contract. + * + * Emits the event `SlashIndicatorContractUpdated`. + * + */ + function _setSlashIndicatorContract(address _addr) internal { + _slashIndicatorContract = ISlashIndicator(_addr); + emit SlashIndicatorContractUpdated(_addr); + } +} diff --git a/contracts/extensions/HasStakingContract.sol b/contracts/extensions/HasStakingContract.sol new file mode 100644 index 000000000..65b582599 --- /dev/null +++ b/contracts/extensions/HasStakingContract.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./HasProxyAdmin.sol"; +import "../interfaces/collections/IHasStakingContract.sol"; +import "../interfaces/IStaking.sol"; + +contract HasStakingContract is IHasStakingContract, HasProxyAdmin { + IStaking internal _stakingContract; + + modifier onlyStakingContract() { + require(stakingContract() == msg.sender, "HasStakingManager: method caller must be staking contract"); + _; + } + + /** + * @inheritdoc IHasStakingContract + */ + function stakingContract() public view override returns (address) { + return address(_stakingContract); + } + + /** + * @inheritdoc IHasStakingContract + */ + function setStakingContract(address _addr) external override onlyAdmin { + _setStakingContract(_addr); + } + + /** + * @dev Sets the staking contract. + * + * Requirements: + * - The new address is a contract. + * + * Emits the event `StakingContractUpdated`. + * + */ + function _setStakingContract(address _addr) internal { + _stakingContract = IStaking(_addr); + emit StakingContractUpdated(_addr); + } +} diff --git a/contracts/extensions/HasStakingVestingContract.sol b/contracts/extensions/HasStakingVestingContract.sol new file mode 100644 index 000000000..331be5d1f --- /dev/null +++ b/contracts/extensions/HasStakingVestingContract.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./HasProxyAdmin.sol"; +import "../interfaces/collections/IHasStakingVestingContract.sol"; +import "../interfaces/IStakingVesting.sol"; + +contract HasStakingVestingContract is IHasStakingVestingContract, HasProxyAdmin { + IStakingVesting internal _stakingVestingContract; + + modifier onlyStakingVestingContract() { + require( + stakingVestingContract() == msg.sender, + "HasStakingVestingContract: method caller must be staking vesting contract" + ); + _; + } + + /** + * @inheritdoc IHasStakingVestingContract + */ + function stakingVestingContract() public view override returns (address) { + return address(_stakingVestingContract); + } + + /** + * @inheritdoc IHasStakingVestingContract + */ + function setStakingVestingContract(address _addr) external override onlyAdmin { + _setStakingVestingContract(_addr); + } + + /** + * @dev Sets the staking vesting contract. + * + * Requirements: + * - The new address is a contract. + * + * Emits the event `StakingVestingContractUpdated`. + * + */ + function _setStakingVestingContract(address _addr) internal { + _stakingVestingContract = IStakingVesting(_addr); + emit StakingVestingContractUpdated(_addr); + } +} diff --git a/contracts/extensions/HasValidatorContract.sol b/contracts/extensions/HasValidatorContract.sol new file mode 100644 index 000000000..9fd95506b --- /dev/null +++ b/contracts/extensions/HasValidatorContract.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./HasProxyAdmin.sol"; +import "../interfaces/collections/IHasValidatorContract.sol"; +import "../interfaces/IRoninValidatorSet.sol"; + +contract HasValidatorContract is IHasValidatorContract, HasProxyAdmin { + IRoninValidatorSet internal _validatorContract; + + modifier onlyValidatorContract() { + require(validatorContract() == msg.sender, "HasValidatorContract: method caller must be validator contract"); + _; + } + + /** + * @inheritdoc IHasValidatorContract + */ + function validatorContract() public view override returns (address) { + return address(_validatorContract); + } + + /** + * @inheritdoc IHasValidatorContract + */ + function setValidatorContract(address _addr) external override onlyAdmin { + _setValidatorContract(_addr); + } + + /** + * @dev Sets the validator contract. + * + * Requirements: + * - The new address is a contract. + * + * Emits the event `ValidatorContractUpdated`. + * + */ + function _setValidatorContract(address _addr) internal { + _validatorContract = IRoninValidatorSet(_addr); + emit ValidatorContractUpdated(_addr); + } +} diff --git a/contracts/interfaces/collections/IHasSlashIndicatorContract.sol b/contracts/interfaces/collections/IHasSlashIndicatorContract.sol new file mode 100644 index 000000000..57d630b0a --- /dev/null +++ b/contracts/interfaces/collections/IHasSlashIndicatorContract.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IHasSlashIndicatorContract { + /// @dev Emitted when the slash indicator contract is updated. + event SlashIndicatorContractUpdated(address); + + /** + * @dev Returns the slash indicator contract. + */ + function slashIndicatorContract() external view returns (address); + + /** + * @dev Sets the slash indicator contract. + * + * Requirements: + * - The method caller is admin. + * - The new address is a contract. + * + * Emits the event `SlashIndicatorContractUpdated`. + * + */ + function setSlashIndicatorContract(address) external; +} diff --git a/contracts/interfaces/collections/IHasStakingContract.sol b/contracts/interfaces/collections/IHasStakingContract.sol new file mode 100644 index 000000000..c285a99be --- /dev/null +++ b/contracts/interfaces/collections/IHasStakingContract.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IHasStakingContract { + /// @dev Emitted when the staking contract is updated. + event StakingContractUpdated(address); + + /** + * @dev Returns the staking contract. + */ + function stakingContract() external view returns (address); + + /** + * @dev Sets the staking contract. + * + * Requirements: + * - The method caller is admin. + * - The new address is a contract. + * + * Emits the event `StakingContractUpdated`. + * + */ + function setStakingContract(address) external; +} diff --git a/contracts/interfaces/collections/IHasStakingVestingContract.sol b/contracts/interfaces/collections/IHasStakingVestingContract.sol new file mode 100644 index 000000000..da3b14e2b --- /dev/null +++ b/contracts/interfaces/collections/IHasStakingVestingContract.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IHasStakingVestingContract { + /// @dev Emitted when the staking vesting contract is updated. + event StakingVestingContractUpdated(address); + + /** + * @dev Returns the staking contract. + */ + function stakingVestingContract() external view returns (address); + + /** + * @dev Sets the staking vesting contract. + * + * Requirements: + * - The method caller is admin. + * - The new address is a contract. + * + * Emits the event `StakingVestingContractUpdated`. + * + */ + function setStakingVestingContract(address) external; +} diff --git a/contracts/interfaces/collections/IHasValidatorContract.sol b/contracts/interfaces/collections/IHasValidatorContract.sol new file mode 100644 index 000000000..184d329f9 --- /dev/null +++ b/contracts/interfaces/collections/IHasValidatorContract.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IHasValidatorContract { + /// @dev Emitted when the validator contract is updated. + event ValidatorContractUpdated(address); + + /** + * @dev Returns the validator contract. + */ + function validatorContract() external view returns (address); + + /** + * @dev Sets the validator contract. + * + * Requirements: + * - The method caller is admin. + * - The new address is a contract. + * + * Emits the event `ValidatorContractUpdated`. + * + */ + function setValidatorContract(address) external; +} From ea5c475fd42e37dac14878e417c022c73e76593a Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 20 Sep 2022 18:28:50 +0700 Subject: [PATCH 144/190] Remove swapping contract & its test cases --- contracts/mocks/MockSwapStorage.sol | 37 ----------- test/sorting/MockSwapStorage.test.ts | 91 ---------------------------- 2 files changed, 128 deletions(-) delete mode 100644 contracts/mocks/MockSwapStorage.sol delete mode 100644 test/sorting/MockSwapStorage.test.ts diff --git a/contracts/mocks/MockSwapStorage.sol b/contracts/mocks/MockSwapStorage.sol deleted file mode 100644 index 7ef601831..000000000 --- a/contracts/mocks/MockSwapStorage.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import "../interfaces/IStaking.sol"; - -contract MockSwapStorage { - /// @dev Array of all validators. - IStaking.ValidatorCandidate[] public validatorSet; - - function pushValidator(IStaking.ValidatorCandidate memory _incomingValidator) external { - validatorSet.push(_incomingValidator); - } - - function _setValidatorAt(uint256 _index, IStaking.ValidatorCandidate memory _incomingValidator) public { - require(_index < validatorSet.length, "Access out-of-bound"); - - IStaking.ValidatorCandidate storage _validator = validatorSet[_index]; - _validator.candidateAdmin = _incomingValidator.candidateAdmin; - _validator.consensusAddr = _incomingValidator.consensusAddr; - _validator.treasuryAddr = _incomingValidator.treasuryAddr; - _validator.commissionRate = _incomingValidator.commissionRate; - _validator.stakedAmount = _incomingValidator.stakedAmount; - _validator.delegatedAmount = _incomingValidator.delegatedAmount; - _validator.governing = _incomingValidator.governing; - } - - function swapValidators(uint256 _i, uint256 _j) external { - require(_i < validatorSet.length, "Access left element out-of-bound"); - require(_j < validatorSet.length, "Access right element out-of-bound"); - IStaking.ValidatorCandidate memory _tmp = validatorSet[_i]; - _setValidatorAt(_i, validatorSet[_j]); - _setValidatorAt(_j, _tmp); - } -} diff --git a/test/sorting/MockSwapStorage.test.ts b/test/sorting/MockSwapStorage.test.ts deleted file mode 100644 index adc28196e..000000000 --- a/test/sorting/MockSwapStorage.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { expect } from 'chai'; -import { ethers } from 'hardhat'; -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; - -import { MockSwapStorage, MockSwapStorage__factory } from '../../src/types'; -import { ValidatorCandidateStruct } from '../../src/types/IStaking'; - -let swapping: MockSwapStorage; - -let signers: SignerWithAddress[]; -let candidates: ValidatorCandidateStruct[]; -let admin: SignerWithAddress; - -const generateCandidate = ( - candidateAdmin: string, - consensusAddr: string, - treasuryAddr: string -): ValidatorCandidateStruct => { - return { - candidateAdmin: candidateAdmin, - consensusAddr: consensusAddr, - treasuryAddr: treasuryAddr, - commissionRate: 0, - stakedAmount: 0, - delegatedAmount: 0, - governing: false, - state: 0, - extraData: '0x', - }; -}; - -describe.skip('Mock swap struct on storage tests', () => { - describe('Stress test', async () => { - before(async () => { - [admin, ...signers] = await ethers.getSigners(); - candidates = []; - swapping = await new MockSwapStorage__factory(admin).deploy(); - - for (let i = 0; i < 33; i++) { - candidates.push( - generateCandidate(signers[3 * i].address, signers[3 * i + 1].address, signers[3 * i + 2].address) - ); - } - }); - - it('Should be able to add 2 validators', async () => { - for (let i = 0; i < 2; i++) { - await swapping.pushValidator(candidates[i]); - } - }); - - it('Should be able to swap 2 existed validators', async () => { - let swapTable = [1, 0]; - console.log(swapTable); - for (let i = 0; i < 2; i++) { - console.log('>>> Swap index', i, 'to', swapTable[i]); - await swapping.swapValidators(i, swapTable[i]); - } - }); - - it('Should be able to add 9 more validators', async () => { - for (let i = 2; i <= 10; i++) { - await swapping.pushValidator(candidates[i]); - } - }); - - it('Should be able to swap 11 existed validators', async () => { - let swapTable = [...Array(11).keys()].map((x) => x).sort(() => 0.5 - Math.random()); - console.log(swapTable); - for (let i = 0; i <= 10; i++) { - console.log('>>> Swap index', i, 'to', swapTable[i]); - await swapping.swapValidators(i, swapTable[i]); - } - }); - - it('Should be able to add 20 validators more', async () => { - for (let i = 11; i <= 30; i++) { - await swapping.pushValidator(candidates[i]); - } - }); - - it('Should be able to swap 31 existed validators', async () => { - let swapTable = [...Array(31).keys()].map((x) => x).sort(() => 0.5 - Math.random()); - console.log(swapTable); - for (let i = 0; i <= 30; i++) { - console.log('>>> Swap index', i, 'to', swapTable[i]); - await swapping.swapValidators(i, swapTable[i]); - } - }); - }); -}); From 71ffd3c2ea625682e0f6878fd0d9000827bbb123 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 20 Sep 2022 18:30:05 +0700 Subject: [PATCH 145/190] [Staking] Fix staking TODOs & remove candidate struct --- contracts/interfaces/IStaking.sol | 158 ++++--------------- contracts/staking/Staking.sol | 207 +----------------------- contracts/staking/StakingManager.sol | 228 ++++++++++++--------------- 3 files changed, 134 insertions(+), 459 deletions(-) diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index c4f424dbb..edc2cbffe 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -5,88 +5,42 @@ pragma solidity ^0.8.9; import "./IRewardPool.sol"; interface IStaking is IRewardPool { - enum ValidatorState { - ACTIVE, - ON_REQUESTING_RENOUNCE, - ON_CONFIRMED_RENOUNCE, - RENOUNCED - } - - struct ValidatorCandidate { - /// @dev The candidate admin that stakes for the validator. - address candidateAdmin; - /// @dev Address of the validator that produces block, e.g. block.coinbase. This is so-called validator address. - address consensusAddr; - /// @dev Address that receives mining reward of the validator - address payable treasuryAddr; - /// @dev The percentile of reward that validators can be received, the rest goes to the delegators. - /// Values in range [0; 100_00] stands for 0-100% - uint256 commissionRate; - /// @dev The RON amount from the validator. + struct PoolDetail { + // Address of the pool + address addr; + // Pool admin address + address admin; + // Self-staked amount uint256 stakedAmount; - /// @dev The RON amount from the delegator. - uint256 delegatedAmount; - /// @dev Mark the validator is a governance node - bool governing; - /// @dev State of the validator - ValidatorState state; - /// @dev Extra data - bytes extraData; + // Total balance of the pool + uint256 totalBalance; + // Mapping from delegator => delegated amount + mapping(address => uint256) delegatedAmount; } - - /// @dev Emitted when the validator candidate is proposed. - event ValidatorProposed(address indexed consensusAddr, address indexed candidateAdmin, uint256 indexed candidateIdx); - /// @dev Emitted when the candidate admin staked for themself. - event Staked(address indexed validator, uint256 amount); - /// @dev Emitted when the candidate admin unstaked the amount of RON from themself. - event Unstaked(address indexed validator, uint256 amount); /// @dev Emitted when the validator candidate requested to renounce. event ValidatorRenounceRequested(address indexed consensusAddr, uint256 amount); /// @dev Emitted when the renounce request is finalized. event ValidatorRenounceFinalized(address indexed consensusAddr, uint256 amount); + /// @dev Emitted when the pool admin staked for themself. + event Staked(address indexed validator, uint256 amount); + /// @dev Emitted when the pool admin unstaked the amount of RON from themself. + event Unstaked(address indexed validator, uint256 amount); /// @dev Emitted when the delegator staked for a validator. event Delegated(address indexed delegator, address indexed validator, uint256 amount); /// @dev Emitted when the delegator unstaked from a validator. event Undelegated(address indexed delegator, address indexed validator, uint256 amount); - /// @dev Emitted when the address of validator contract is updated. - event ValidatorContractUpdated(address); - /// @dev Emitted when the address of governance admin is updated. - event GovernanceAdminUpdated(address); /// @dev Emitted when the minimum balance for being a validator is updated. event MinValidatorBalanceUpdated(uint256 threshold); - /// @dev Emitted when the maximum number of validator candidates is updated. - event MaxValidatorCandidateUpdated(uint256 threshold); /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR GOVERNANCE // /////////////////////////////////////////////////////////////////////////////////////// - /** - * @dev Returns the governance admin address. - */ - function governanceAdmin() external view returns (address); - - /** - * @dev Returns validator contract - */ - function validatorContract() external view returns (address); - /** * @dev Returns the minimum threshold for being a validator candidate. */ function minValidatorBalance() external view returns (uint256); - /** - * @dev Updates the governance admin - * - * Requirements: - * - The method caller is the governance admin - * - * Emits the event `GovernanceAdminUpdated` - * - */ - function setGovernanceAdmin(address _governanceAdmin) external; - /** * @dev Sets the minimum threshold for being a validator candidate. * @@ -98,41 +52,10 @@ interface IStaking is IRewardPool { */ function setMinValidatorBalance(uint256) external; - /** - * @dev Returns the maximum number of validator candidate. - */ - function maxValidatorCandidate() external view returns (uint256); - - /** - * @dev Sets the maximum number of validator candidate. - * - * Requirements: - * - The method caller is governance admin. - * - * Emits the `MaxValidatorCandidateUpdated` event. - * - */ - function setMaxValidatorCandidate(uint256) external; - /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR VALIDATOR CONTRACT // /////////////////////////////////////////////////////////////////////////////////////// - /** - * @dev Returns the validator candidate list. - */ - function getValidatorCandidates() external view returns (ValidatorCandidate[] memory candidates); - - /** - * @dev Returns the current candidate length. - */ - function getValidatorCandidateLength() external view returns (uint256); - - /** - * @dev Returns the validator candidate weights. - */ - function getCandidateWeights() external view returns (address[] memory _candidates, uint256[] memory _weights); - /** * @dev Records the amount of reward `_reward` for the pending pool `_poolAddr`. * @@ -141,7 +64,7 @@ interface IStaking is IRewardPool { * * Emits the `PendingPoolUpdated` event. * - * @notice This method should not be called after the pending pool is sinked. + * Note: This method should not be called after the pending pool is sinked. * */ function recordReward(address _consensusAddr, uint256 _reward) external payable; @@ -179,26 +102,6 @@ interface IStaking is IRewardPool { */ function deductStakingAmount(address _consensusAddr, uint256 _amount) external; - /** - * @dev Returns the commission rate of the validator candidate `_consensusAddr`. - * - * Values in [0; 100_00] stands for 0-100%. - * - * Requirements: - * - The validator candidate is already existed. - * - */ - function commissionRateOf(address _consensusAddr) external view returns (uint256 _rate); - - /** - * @dev Returns the treasury address of the validator candidate `_consensusAddr`. - * - * Requirements: - * - The validator candidate is already existed. - * - */ - function treasuryAddressOf(address _consensusAddr) external view returns (address); - /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR VALIDATOR CANDIDATE // /////////////////////////////////////////////////////////////////////////////////////// @@ -207,26 +110,24 @@ interface IStaking is IRewardPool { * @dev Proposes a candidate to become a valdiator. * * Requirements: - * - The validator length is not exceeded the total validator threshold `maxValidatorCandidate`. + * - The validator length is not exceeded the total validator threshold `maxValidatorCandidate()`. * - The amount is larger than or equal to the minimum validator balance `minValidatorBalance()`. * - * Emits the `ValidatorProposed` event. - * - * @return _candidateIdx The index of the candidate in the validator candidate list. + * Emits the `Staked` event and the `Delegated` event. * */ function proposeValidator( address _consensusAddr, address payable _treasuryAddr, uint256 _commissionRate - ) external payable returns (uint256 _candidateIdx); + ) external payable; /** * @dev Self-delegates to the validator candidate `_consensusAddr`. * * Requirements: - * - The candidate `_consensusAddr` is already existent. - * - The method caller is the candidate admin. + * - The consensus address is a validator candidate. + * - The method caller is the pool admin. * - The `msg.value` is larger than 0. * * Emits the `Staked` event and the `Delegated` event. @@ -238,8 +139,8 @@ interface IStaking is IRewardPool { * @dev Unstakes from the validator candidate `_consensusAddr` for `_amount`. * * Requirements: - * - The candidate `_consensusAddr` is already existent. - * - The method caller is the candidate admin. + * - The consensus address is a validator candidate. + * - The method caller is the pool admin. * * Emits the `Unstaked` event and the `Undelegated` event. * @@ -250,8 +151,8 @@ interface IStaking is IRewardPool { * @dev Renounces being a validator candidate and takes back the delegated/staked amount. * * Requirements: - * - The candidate `_consensusAddr` is already existent. - * - The method caller is the candidate admin. + * - The consensus address is a validator candidate. + * - The method caller is the pool admin. * */ function renounce(address consensusAddr) external; @@ -264,7 +165,8 @@ interface IStaking is IRewardPool { * @dev Stakes for a validator candidate `_consensusAddr`. * * Requirements: - * - The method caller is not the candidate admin. + * - The consensus address is a validator candidate. + * - The method caller is not the pool admin. * * Emits the `Delegated` event. * @@ -275,7 +177,7 @@ interface IStaking is IRewardPool { * @dev Unstakes from a validator candidate `_consensusAddr` for `_amount`. * * Requirements: - * - The method caller is not the candidate admin. + * - The method caller is not the pool admin. * * Emits the `Undelegated` event. * @@ -286,7 +188,8 @@ interface IStaking is IRewardPool { * @dev Unstakes an amount of RON from the `_consensusAddrSrc` and stake for `_consensusAddrDst`. * * Requirements: - * - The method caller is not the candidate admin. + * - The method caller is not the pool admin. + * - The consensus address `_consensusAddrDst` is a validator candidate. * * Emits the `Undelegated` event and the `Delegated` event. * @@ -317,7 +220,8 @@ interface IStaking is IRewardPool { * @dev Claims the rewards and delegates them to the consensus address. * * Requirements: - * - The method caller is not the candidate admin. + * - The method caller is not the pool admin. + * - The consensus address `_consensusAddrDst` is a validator candidate. * * Emits the `RewardClaimed` event and the `Delegated` event. * diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 0076de0da..cc41f6bd7 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -11,30 +11,9 @@ import "./StakingManager.sol"; contract Staking is IStaking, StakingManager, Initializable { /// @dev The minimum threshold for being a validator candidate. uint256 internal _minValidatorBalance; - /// @dev Maximum number of validator. - uint256 internal _maxValidatorCandidate; - /// @dev Governance admin address. - address internal _governanceAdmin; - /// @dev Validator contract address. - address internal _validatorContract; // Change type to address for testing purpose - - /// @dev Mapping from consensus address => bitwise negation of validator index in `validatorCandidates`. - mapping(address => uint256) internal _candidateIndex; - /// @dev The validator candidate array. - ValidatorCandidate[] public validatorCandidates; - /// @dev Mapping from consensus address => period index => indicating the pending reward in the period is sinked or not. + /// @dev Mapping from pool address => period index => indicating the pending reward in the period is sinked or not. mapping(address => mapping(uint256 => bool)) internal _pRewardSinked; - modifier onlyGovernanceAdmin() { - require(msg.sender == _governanceAdmin, "Staking: method caller is not governance admin"); - _; - } - - modifier onlyValidatorContract() { - require(msg.sender == _validatorContract, "Staking: method caller is not the validator contract"); - _; - } - constructor() { _disableInitializers(); } @@ -46,15 +25,8 @@ contract Staking is IStaking, StakingManager, Initializable { /** * @dev Initializes the contract storage. */ - function initialize( - address __validatorContract, - address __governanceAdmin, - uint256 __maxValidatorCandidate, - uint256 __minValidatorBalance - ) external initializer { + function initialize(address __validatorContract, uint256 __minValidatorBalance) external initializer { _setValidatorContract(__validatorContract); - _setGovernanceAdmin(__governanceAdmin); - _setMaxValidatorCandidate(__maxValidatorCandidate); _setMinValidatorBalance(__minValidatorBalance); } @@ -62,24 +34,6 @@ contract Staking is IStaking, StakingManager, Initializable { // FUNCTIONS FOR GOVERNANCE // /////////////////////////////////////////////////////////////////////////////////////// - /** - * @inheritdoc IStaking - */ - function governanceAdmin() public view override returns (address) { - return _governanceAdmin; - } - - function validatorContract() external view override returns (address) { - return _validatorContract; - } - - /** - * @inheritdoc IStaking - */ - function setGovernanceAdmin(address _newAddr) external override onlyGovernanceAdmin { - _setGovernanceAdmin(_newAddr); - } - /** * @inheritdoc IStaking */ @@ -90,67 +44,14 @@ contract Staking is IStaking, StakingManager, Initializable { /** * @inheritdoc IStaking */ - function setMinValidatorBalance(uint256 _threshold) external override onlyGovernanceAdmin { + function setMinValidatorBalance(uint256 _threshold) external override onlyAdmin { _setMinValidatorBalance(_threshold); } - /** - * @inheritdoc IStaking - */ - function maxValidatorCandidate() public view override(IStaking, StakingManager) returns (uint256) { - return _maxValidatorCandidate; - } - - /** - * @inheritdoc IStaking - */ - function setMaxValidatorCandidate(uint256 _threshold) external override onlyGovernanceAdmin { - _setMaxValidatorCandidate(_threshold); - } - - /** - * @inheritdoc IStaking - */ - function getValidatorCandidateLength() public view override(IStaking, StakingManager) returns (uint256) { - return validatorCandidates.length; - } - /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR VALIDATOR // /////////////////////////////////////////////////////////////////////////////////////// - /** - * @inheritdoc IStaking - */ - function getValidatorCandidates() public view returns (ValidatorCandidate[] memory) { - return validatorCandidates; - } - - /** - * @inheritdoc IStaking - */ - function getCandidateWeights() external view returns (address[] memory _candidates, uint256[] memory _weights) { - uint256 _length = validatorCandidates.length; - uint256 _newLength = _length; - _candidates = new address[](_length); - _weights = new uint256[](_length); - - for (uint256 _i; _i < _length; _i++) { - ValidatorCandidate storage _candidate = validatorCandidates[_i]; - if (_candidate.stakedAmount < _minValidatorBalance) { - _newLength--; - continue; - } - _candidates[_i] = _candidate.consensusAddr; - _weights[_i] = _candidate.delegatedAmount; - } - - assembly { - mstore(_candidates, _newLength) - mstore(_weights, _newLength) - } - } - /** * @inheritdoc IStaking */ @@ -181,81 +82,14 @@ contract Staking is IStaking, StakingManager, Initializable { * @inheritdoc IStaking */ function deductStakingAmount(address _consensusAddr, uint256 _amount) external onlyValidatorContract { - ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); - _unstake(_candidate, _candidate.candidateAdmin, _amount); - } - - /** - * @inheritdoc IStaking - */ - function commissionRateOf(address _consensusAddr) external view returns (uint256 _rate) { - ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); - return _candidate.commissionRate; - } - - /** - * @inheritdoc IStaking - */ - function treasuryAddressOf(address _consensusAddr) external view returns (address) { - ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); - return _candidate.treasuryAddr; + PoolDetail storage _pool = _stakingPool[_consensusAddr]; + _unstake(_pool, _pool.admin, _amount); } /////////////////////////////////////////////////////////////////////////////////////// // HELPER FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// - /** - * @inheritdoc StakingManager - */ - function _getCandidate(address _consensusAddr) - internal - view - override - returns (ValidatorCandidate storage _candidate) - { - uint256 _idx = _candidateIndex[_consensusAddr]; - require(_idx > 0, "Staking: query for nonexistent candidate"); - _candidate = validatorCandidates[~_idx]; - } - - /** - * @inheritdoc StakingManager - */ - function _getCandidateIndex(address _consensusAddr) internal view override returns (uint256) { - return _candidateIndex[_consensusAddr]; - } - - /** - * @inheritdoc StakingManager - */ - function _setCandidateIndex(address _consensusAddr, uint256 _candidateIdx) internal override { - _candidateIndex[_consensusAddr] = _candidateIdx; - } - - /** - * @dev Sets the governance admin address. - * - * Emits the `GovernanceAdminUpdated` event. - * - */ - function _setGovernanceAdmin(address _newAddr) internal { - require(_newAddr != address(0), "Staking: Cannot set admin to zero address"); - _governanceAdmin = _newAddr; - emit GovernanceAdminUpdated(_newAddr); - } - - /** - * @dev Sets the governance admin address. - * - * Emits the `ValidatorContractUpdated` event. - * - */ - function _setValidatorContract(address _newValidatorContract) internal { - _validatorContract = _newValidatorContract; - emit ValidatorContractUpdated(_newValidatorContract); - } - /** * @dev Sets the minimum threshold for being a validator candidate. * @@ -267,20 +101,6 @@ contract Staking is IStaking, StakingManager, Initializable { emit MinValidatorBalanceUpdated(_threshold); } - /** - * @dev Sets the maximum number of validator candidate. - * - * Requirements: - * - The method caller is governance admin. - * - * Emits the `MaxValidatorCandidateUpdated` event. - * - */ - function _setMaxValidatorCandidate(uint256 _threshold) internal { - _maxValidatorCandidate = _threshold; - emit MaxValidatorCandidateUpdated(_threshold); - } - /** * @inheritdoc RewardCalculation */ @@ -294,21 +114,4 @@ contract Staking is IStaking, StakingManager, Initializable { function _periodOf(uint256 _block) internal view virtual override returns (uint256) { return IRoninValidatorSet(_validatorContract).periodOf(_block); } - - /** - * @inheritdoc StakingManager - */ - function _createValidatorCandidate( - address _consensusAddr, - address _candidateAdmin, - address payable _treasuryAddr, - uint256 _commissionRate - ) internal virtual override returns (ValidatorCandidate memory) { - ValidatorCandidate storage _candidate = validatorCandidates.push(); - _candidate.consensusAddr = _consensusAddr; - _candidate.candidateAdmin = _candidateAdmin; - _candidate.treasuryAddr = _treasuryAddr; - _candidate.commissionRate = _commissionRate; - return _candidate; - } } diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 2b656dc65..7e28dd046 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -4,21 +4,35 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "../extensions/RONTransferHelper.sol"; +import "../extensions/HasValidatorContract.sol"; import "../interfaces/IStaking.sol"; import "./RewardCalculation.sol"; -abstract contract StakingManager is IStaking, RONTransferHelper, ReentrancyGuard, RewardCalculation { - /// @dev Mapping from pool address => delegator address => delegated amount. - mapping(address => mapping(address => uint256)) internal _delegatedAmount; +abstract contract StakingManager is + IStaking, + RONTransferHelper, + ReentrancyGuard, + RewardCalculation, + HasValidatorContract +{ + /// @dev Mapping from pool address => staking pool detail + mapping(address => PoolDetail) internal _stakingPool; modifier noEmptyValue() { require(msg.value > 0, "StakingManager: query with empty value"); _; } - modifier notCandidateAdmin(address _consensusAddr) { - ValidatorCandidate memory _candidate = _getCandidate(_consensusAddr); - require(msg.sender != _candidate.candidateAdmin, "StakingManager: method caller must not be the candidate admin"); + modifier notPoolAdmin(PoolDetail storage _pool, address _delegator) { + require(_pool.admin != _delegator, "StakingManager: delegator must not be the candidate admin"); + _; + } + + modifier onlyValidatorCandidate(address _poolAddr) { + require( + _validatorContract.isValidatorCandidate(_poolAddr), + "StakingManager: method caller must not be the candidate admin" + ); _; } @@ -31,31 +45,35 @@ abstract contract StakingManager is IStaking, RONTransferHelper, ReentrancyGuard override(IRewardPool, RewardCalculation) returns (uint256) { - return _delegatedAmount[_poolAddr][_user]; + return _stakingPool[_poolAddr].delegatedAmount[_user]; } /** * @inheritdoc IRewardPool */ function totalBalance(address _poolAddr) public view override(IRewardPool, RewardCalculation) returns (uint256) { - ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); - return _candidate.delegatedAmount; + return _stakingPool[_poolAddr].totalBalance; } /** - * @inheritdoc IStaking + * @inheritdoc IRewardPool */ - function minValidatorBalance() public view virtual returns (uint256); + function totalBalances(address[] calldata _poolList) public view override returns (uint256[] memory _balances) { + _balances = new uint256[](_poolList.length); + for (uint _i = 0; _i < _poolList.length; _i++) { + _balances[_i] = totalBalance(_poolList[_i]); + } + } /** * @inheritdoc IStaking */ - function maxValidatorCandidate() public view virtual returns (uint256); + function minValidatorBalance() public view virtual returns (uint256); - /** - * @dev IStaking - */ - function getValidatorCandidateLength() public view virtual returns (uint256); + // /** + // * @inheritdoc IStaking + // */ + // function maxValidatorCandidate() public view virtual returns (uint256); /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR VALIDATOR CANDIDATE // @@ -68,40 +86,42 @@ abstract contract StakingManager is IStaking, RONTransferHelper, ReentrancyGuard address _consensusAddr, address payable _treasuryAddr, uint256 _commissionRate - ) external payable override nonReentrant returns (uint256 _candidateIdx) { + ) external payable override nonReentrant { uint256 _amount = msg.value; address payable _candidateAdmin = payable(msg.sender); - _candidateIdx = _proposeValidator(_candidateAdmin, _consensusAddr, _treasuryAddr, _commissionRate, _amount); - _stake(_consensusAddr, _candidateAdmin, _amount); + _proposeValidator(_candidateAdmin, _consensusAddr, _treasuryAddr, _commissionRate, _amount); + _stake(_stakingPool[_consensusAddr], _candidateAdmin, _amount); } /** * @inheritdoc IStaking */ - function stake(address _consensusAddr) external payable override noEmptyValue { - _stake(_consensusAddr, msg.sender, msg.value); + function stake(address _consensusAddr) external payable override noEmptyValue onlyValidatorCandidate(_consensusAddr) { + _stake(_stakingPool[_consensusAddr], msg.sender, msg.value); } /** * @inheritdoc IStaking */ - function unstake(address _consensusAddr, uint256 _amount) external override nonReentrant { + function unstake(address _consensusAddr, uint256 _amount) + external + override + nonReentrant + onlyValidatorCandidate(_consensusAddr) + { address _delegator = msg.sender; - ValidatorCandidate storage _candidate = _getCandidate(_consensusAddr); + PoolDetail storage _pool = _stakingPool[_consensusAddr]; + uint256 _remainAmount = _pool.stakedAmount - _amount; + require(_remainAmount >= minValidatorBalance(), "StakingManager: invalid staked amount left"); - uint256 remainAmount = _candidate.stakedAmount - _amount; - require(remainAmount >= minValidatorBalance(), "StakingManager: invalid staked amount left"); - - _unstake(_candidate, _delegator, _amount); + _unstake(_pool, _delegator, _amount); require(_sendRON(payable(_delegator), _amount), "StakingManager: could not transfer RON"); } /** * @inheritdoc IStaking */ - function renounce( - address /* _consensusAddr */ - ) external { + function renounce(address _consensusAddr) external onlyValidatorCandidate(_consensusAddr) { // TODO(Thor): implement this function revert("unimplemented"); } @@ -110,14 +130,9 @@ abstract contract StakingManager is IStaking, RONTransferHelper, ReentrancyGuard * @dev Proposes a candidate to become a valdiator. * * Requirements: - * - * - The validator length is not exceeded the total validator threshold `_maxValidatorCandidate`. * - The amount is larger than or equal to the minimum validator balance `_minValidatorBalance`. * - * Emits the `ValidatorProposed` event. - * - * @return _candidateIdx The bitwise negative of candidate index. - * */ function _proposeValidator( address payable _candidateAdmin, @@ -125,19 +140,12 @@ abstract contract StakingManager is IStaking, RONTransferHelper, ReentrancyGuard address payable _treasuryAddr, uint256 _commissionRate, uint256 _amount - ) internal returns (uint256 _candidateIdx) { + ) internal { require(_sendRON(_candidateAdmin, 0), "StakingManager: candidate admin cannot receive RON"); require(_sendRON(_treasuryAddr, 0), "StakingManager: treasury cannot receive RON"); - uint256 _length = getValidatorCandidateLength(); - require(_length < maxValidatorCandidate(), "StakingManager: exceeds maximum number of candidates"); - require(_getCandidateIndex(_consensusAddr) == 0, "StakingManager: query for existed candidate"); require(_amount >= minValidatorBalance(), "StakingManager: insufficient amount"); - _candidateIdx = ~_length; - _setCandidateIndex(_consensusAddr, _candidateIdx); - _createValidatorCandidate(_consensusAddr, _candidateAdmin, _treasuryAddr, _commissionRate); - - emit ValidatorProposed(_consensusAddr, _candidateAdmin, _length); + _validatorContract.addValidatorCandidate(_consensusAddr, _treasuryAddr, _commissionRate); } /** @@ -150,39 +158,38 @@ abstract contract StakingManager is IStaking, RONTransferHelper, ReentrancyGuard * */ function _stake( - address _poolAddr, - address _user, + PoolDetail storage _pool, + address _requester, uint256 _amount ) internal { - ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); - require(_candidate.candidateAdmin == _user, "StakingManager: user is not the candidate admin"); + require(_pool.admin == _requester, "StakingManager: requester is not the candidate admin"); + _pool.stakedAmount += _amount; + emit Staked(_pool.addr, _amount); - _candidate.stakedAmount += _amount; - emit Staked(_poolAddr, _amount); - - _delegate(_poolAddr, _user, _amount); + _delegate(_pool, _requester, _amount); } /** * @dev Withdraws the staked amount `_amount` for the validator candidate. * * Requirements: + * - The address `_requester` must be the candidate admin. * - The remain balance must be greater than the minimum validator candidate thresold `minValidatorBalance()`. * * Emits the `Unstaked` event. * */ function _unstake( - ValidatorCandidate storage _candidate, - address _user, + PoolDetail storage _pool, + address _requester, uint256 _amount ) internal { - require(_candidate.candidateAdmin == _user, "StakingManager: user is not the candidate admin"); - require(_amount <= _candidate.stakedAmount, "StakingManager: insufficient staked amount"); + require(_pool.admin == _requester, "StakingManager: requester is not the pool admin"); + require(_amount <= _pool.stakedAmount, "StakingManager: insufficient staked amount"); - _candidate.stakedAmount -= _amount; - emit Unstaked(_candidate.consensusAddr, _amount); - _undelegate(_candidate.consensusAddr, _user, _amount); + _pool.stakedAmount -= _amount; + emit Unstaked(_pool.addr, _amount); + _undelegate(_pool, _requester, _amount); } /////////////////////////////////////////////////////////////////////////////////////// @@ -192,16 +199,17 @@ abstract contract StakingManager is IStaking, RONTransferHelper, ReentrancyGuard /** * @inheritdoc IStaking */ - function delegate(address _consensusAddr) external payable noEmptyValue notCandidateAdmin(_consensusAddr) { - _delegate(_consensusAddr, msg.sender, msg.value); + function delegate(address _consensusAddr) external payable noEmptyValue onlyValidatorCandidate(_consensusAddr) { + _delegate(_stakingPool[_consensusAddr], msg.sender, msg.value); } /** * @inheritdoc IStaking */ - function undelegate(address _consensusAddr, uint256 _amount) external notCandidateAdmin(_consensusAddr) nonReentrant { + function undelegate(address _consensusAddr, uint256 _amount) external nonReentrant { + // TODO: add bulk function to undelegate a list of consensus addresses address payable _delegator = payable(msg.sender); - _undelegate(_consensusAddr, _delegator, _amount); + _undelegate(_stakingPool[_consensusAddr], _delegator, _amount); require(_sendRON(_delegator, _amount), "StakingManager: could not transfer RON"); } @@ -212,10 +220,10 @@ abstract contract StakingManager is IStaking, RONTransferHelper, ReentrancyGuard address _consensusAddrSrc, address _consensusAddrDst, uint256 _amount - ) external notCandidateAdmin(_consensusAddrDst) { + ) external { address _delegator = msg.sender; - _undelegate(_consensusAddrSrc, _delegator, _amount); - _delegate(_consensusAddrDst, _delegator, _amount); + _undelegate(_stakingPool[_consensusAddrSrc], _delegator, _amount); + _delegate(_stakingPool[_consensusAddrDst], _delegator, _amount); } /** @@ -251,7 +259,6 @@ abstract contract StakingManager is IStaking, RONTransferHelper, ReentrancyGuard function delegateRewards(address[] calldata _consensusAddrList, address _consensusAddrDst) external override - notCandidateAdmin(_consensusAddrDst) returns (uint256 _amount) { return _delegateRewards(msg.sender, _consensusAddrList, _consensusAddrDst); @@ -261,52 +268,50 @@ abstract contract StakingManager is IStaking, RONTransferHelper, ReentrancyGuard * @dev Delegates from a validator address. * * Requirements: - * - The validator is an existed candidate. + * - The delegator is not the pool admin. * * Emits the `Delegated` event. * - * @notice This function does not verify the `msg.value` with the amount. + * Note: This function does not verify the `msg.value` with the amount. * */ function _delegate( - address _poolAddr, - address _user, + PoolDetail storage _pool, + address _delegator, uint256 _amount - ) internal { - uint256 _newBalance = _delegatedAmount[_poolAddr][_user] + _amount; - _syncUserReward(_poolAddr, _user, _newBalance); + ) internal notPoolAdmin(_pool, _delegator) { + uint256 _newBalance = _pool.delegatedAmount[_delegator] + _amount; + _syncUserReward(_pool.addr, _delegator, _newBalance); - ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); - _candidate.delegatedAmount += _amount; - _delegatedAmount[_poolAddr][_user] = _newBalance; - emit Delegated(_user, _poolAddr, _amount); + _pool.totalBalance += _amount; + _pool.delegatedAmount[_delegator] = _newBalance; + emit Delegated(_delegator, _pool.addr, _amount); } /** * @dev Undelegates from a validator address. * * Requirements: - * - The validator is an existed candidate. + * - The delegator is not the pool admin. * - The delegated amount is larger than or equal to the undelegated amount. * * Emits the `Undelegated` event. * - * @notice Consider transferring back the amount of RON after calling this function. + * Note: Consider transferring back the amount of RON after calling this function. + * */ function _undelegate( - address _poolAddr, - address _user, + PoolDetail storage _pool, + address _delegator, uint256 _amount - ) private { - require(_delegatedAmount[_poolAddr][_user] >= _amount, "StakingManager: insufficient amount to undelegate"); - - uint256 _newBalance = _delegatedAmount[_poolAddr][_user] - _amount; - _syncUserReward(_poolAddr, _user, _newBalance); - - ValidatorCandidate storage _candidate = _getCandidate(_poolAddr); - _candidate.delegatedAmount -= _amount; - _delegatedAmount[_poolAddr][_user] = _newBalance; - emit Undelegated(_user, _poolAddr, _amount); + ) private notPoolAdmin(_pool, _delegator) { + require(_pool.delegatedAmount[_delegator] >= _amount, "StakingManager: insufficient amount to undelegate"); + + uint256 _newBalance = _pool.delegatedAmount[_delegator] - _amount; + _syncUserReward(_pool.addr, _delegator, _newBalance); + _pool.totalBalance -= _amount; + _pool.delegatedAmount[_delegator] = _newBalance; + emit Undelegated(_delegator, _pool.addr, _amount); } /** @@ -328,43 +333,6 @@ abstract contract StakingManager is IStaking, RONTransferHelper, ReentrancyGuard address _poolAddrDst ) internal returns (uint256 _amount) { _amount = _claimRewards(_user, _poolAddrList); - _delegate(_poolAddrDst, _user, _amount); + _delegate(_stakingPool[_poolAddrDst], _user, _amount); } - - /////////////////////////////////////////////////////////////////////////////////////// - // HELPER FUNCTIONS // - /////////////////////////////////////////////////////////////////////////////////////// - - /** - * @dev Returns the validator candidate from storage form. - * - * Requirements: - * - The candidate is already existed. - * - */ - function _getCandidate(address _consensusAddr) internal view virtual returns (ValidatorCandidate storage _candidate); - - /** - * @dev Returns the bitwise negation of the candidate index in the response of function `getValidator Candidates()`. - * - * Requirements: - * - The candidate is already existed. - * - */ - function _getCandidateIndex(address _consensusAddr) internal view virtual returns (uint256); - - /** - * @dev Sets the candidate index. - */ - function _setCandidateIndex(address _consensusAddr, uint256 _candidateIdx) internal virtual; - - /** - * @dev Creates new validator candidate in the storage and returns its struct. - */ - function _createValidatorCandidate( - address _consensusAddr, - address _candidateAdmin, - address payable _treasuryAddr, - uint256 _commissionRate - ) internal virtual returns (ValidatorCandidate memory); } From b7ed226a0cacd3823b03115e78802c70f404ffa6 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 20 Sep 2022 18:31:40 +0700 Subject: [PATCH 146/190] [Validator] Add candidate storage --- contracts/interfaces/IRoninValidatorSet.sol | 53 ++---- contracts/validator/CandidateManager.sol | 127 +++++++++++++++ .../{ => validator}/RoninValidatorSet.sol | 154 ++++++------------ 3 files changed, 183 insertions(+), 151 deletions(-) create mode 100644 contracts/validator/CandidateManager.sol rename contracts/{ => validator}/RoninValidatorSet.sol (78%) diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index 4b0d3ff40..8eef4002b 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -2,9 +2,17 @@ pragma solidity ^0.8.9; -import "./ISlashIndicator.sol"; +import "./ICandidateManager.sol"; -interface IRoninValidatorSet { +interface IRoninValidatorSet is ICandidateManager { + /// @dev Emitted when the number of max validator is updated + event MaxValidatorNumberUpdated(uint256); + /// @dev Emitted when the number of blocks in epoch is updated + event NumberOfBlocksInEpochUpdated(uint256); + /// @dev Emitted when the number of epochs in period is updated + event NumberOfEpochsInPeriodUpdated(uint256); + /// @dev Emitted when the validator set is updated + event ValidatorSetUpdated(address[]); /// @dev Emitted when the reward of the valdiator is deprecated. event RewardDeprecated(address coinbaseAddr, uint256 rewardAmount); /// @dev Emitted when the block reward is submitted. @@ -15,16 +23,6 @@ interface IRoninValidatorSet { event MiningRewardDistributed(address validatorAddr, uint256 amount); /// @dev Emitted when the amount of RON reward is distributed. event StakingRewardDistributed(uint256 amount); - /// @dev Emitted when the validator set is updated - event ValidatorSetUpdated(address[]); - /// @dev Emitted when the governance admin is updated - event GovernanceAdminUpdated(address); - /// @dev Emitted when the number of max validator is updated - event MaxValidatorNumberUpdated(uint256); - /// @dev Emitted when the number of blocks in epoch is updated - event NumberOfBlocksInEpochUpdated(uint256); - /// @dev Emitted when the number of epochs in period is updated - event NumberOfEpochsInPeriodUpdated(uint256); /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR COINBASE // @@ -66,26 +64,6 @@ interface IRoninValidatorSet { // FUNCTIONS FOR SLASH INDICATOR // /////////////////////////////////////////////////////////////////////////////////////// - /** - * @dev Returns the governance admin address. - */ - function governanceAdmin() external view returns (address); - - /** - * @dev Returns the slash indicator contract address. - */ - function slashIndicatorContract() external view returns (address); - - /** - * @dev Returns the staking contract address. - */ - function stakingContract() external view returns (address); - - /** - * @dev Returns the staking vesting contract address. - */ - function stakingVestingContract() external view returns (address); - /** * @dev Slashes the validator. * @@ -159,17 +137,6 @@ interface IRoninValidatorSet { // FUNCTIONS FOR GOVERNANCE ADMIN // /////////////////////////////////////////////////////////////////////////////////////// - /** - * @dev Updates the governance admin - * - * Requirements: - * - The method caller is the governance admin - * - * Emits the event `GovernanceAdminUpdated` - * - */ - function setGovernanceAdmin(address _governanceAdmin) external; - /** * @dev Updates the max validator number * diff --git a/contracts/validator/CandidateManager.sol b/contracts/validator/CandidateManager.sol new file mode 100644 index 000000000..ac35eb7b3 --- /dev/null +++ b/contracts/validator/CandidateManager.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../extensions/HasStakingContract.sol"; +import "../interfaces/ICandidateManager.sol"; +import "../interfaces/IStaking.sol"; +import "../libraries/Sorting.sol"; + +contract CandidateManager is ICandidateManager, HasStakingContract { + /// @dev Maximum number of validator candidate + uint256 private _maxValidatorCandidate; + + /// @dev The validator candidate array + address[] internal _candidates; + /// @dev Mapping from candidate address => bitwise negation of validator index in `_candidates` + mapping(address => uint256) internal _candidateIndex; + /// @dev Mapping from candidate address => their info + mapping(address => ValidatorCandidate) internal _candidateInfo; + + /** + * @inheritdoc ICandidateManager + */ + function maxValidatorCandidate() public view override returns (uint256) { + return _maxValidatorCandidate; + } + + /** + * @inheritdoc ICandidateManager + */ + function setMaxValidatorCandidate(uint256 _number) external override onlyAdmin { + _setMaxValidatorCandidate(_number); + } + + /** + * @inheritdoc ICandidateManager + */ + function addValidatorCandidate( + address _consensusAddr, + address payable _treasuryAddr, + uint256 _commissionRate + ) external override onlyStakingContract { + uint256 _length = _candidates.length; + require(_length < maxValidatorCandidate(), "StakingManager: exceeds maximum number of candidates"); + require(!isValidatorCandidate(_consensusAddr), "CandidateManager: query for already existent candidate"); + + _candidateIndex[_consensusAddr] = ~_length; + _candidates.push(_consensusAddr); + _candidateInfo[_consensusAddr] = ValidatorCandidate(_consensusAddr, _treasuryAddr, _commissionRate, new bytes(0)); + emit ValidatorCandidateAdded(_consensusAddr, _treasuryAddr, _candidateIndex[_consensusAddr]); + } + + /** + * @inheritdoc ICandidateManager + */ + function syncCandidate() public override returns (uint256[] memory _balances) { + IStaking _staking = _stakingContract; + uint256 _minBalance = _staking.minValidatorBalance(); + _balances = _staking.totalBalances(_candidates); + + uint256 _length = _candidates.length; + for (uint _i = 0; _i < _length; _i++) { + if (_balances[_i] < _minBalance) { + _balances[_i] = _balances[--_length]; + _removeCandidate(_candidates[_i]); + } + } + + assembly { + mstore(_balances, _length) + } + } + + /** + * @inheritdoc ICandidateManager + */ + function isValidatorCandidate(address _addr) public view override returns (bool) { + return _candidateIndex[_addr] != 0; + } + + /** + * @inheritdoc ICandidateManager + */ + function getCandidateInfos() external view override returns (ValidatorCandidate[] memory _list) { + _list = new ValidatorCandidate[](_candidates.length); + for (uint _i = 0; _i < _list.length; _i++) { + _list[_i] = _candidateInfo[_candidates[_i]]; + } + } + + /** + * @inheritdoc ICandidateManager + */ + function getValidatorCandidates() external view override returns (address[] memory) { + return _candidates; + } + + /** + * @dev Removes the candidate. + */ + function _removeCandidate(address _addr) internal { + uint256 _idx = _candidateIndex[_addr]; + if (_idx == 0) { + return; + } + + delete _candidateInfo[_addr]; + delete _candidateIndex[_addr]; + + address _lastCandidate = _candidates[_candidates.length - 1]; + _candidateIndex[_lastCandidate] = _idx; + + _candidates[~_idx] = _lastCandidate; + _candidates.pop(); + } + + /** + * @dev Sets the maximum number of validator candidate. + * + * Emits the `MaxValidatorCandidateUpdated` event. + * + */ + function _setMaxValidatorCandidate(uint256 _threshold) internal { + _maxValidatorCandidate = _threshold; + emit MaxValidatorCandidateUpdated(_threshold); + } +} diff --git a/contracts/RoninValidatorSet.sol b/contracts/validator/RoninValidatorSet.sol similarity index 78% rename from contracts/RoninValidatorSet.sol rename to contracts/validator/RoninValidatorSet.sol index 9ca7ce253..a170bf4ad 100644 --- a/contracts/RoninValidatorSet.sol +++ b/contracts/validator/RoninValidatorSet.sol @@ -4,33 +4,26 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import "./extensions/RONTransferHelper.sol"; -import "./interfaces/ISlashIndicator.sol"; -import "./interfaces/IStaking.sol"; -import "./interfaces/IStakingVesting.sol"; -import "./interfaces/IRoninValidatorSet.sol"; -import "./libraries/Sorting.sol"; -import "./libraries/Math.sol"; - -contract RoninValidatorSet is IRoninValidatorSet, RONTransferHelper, Initializable { - /// @dev Governance admin address. - address internal _governanceAdmin; - /// @dev Slash indicator contract address. - address internal _slashIndicatorContract; // Change type to address for testing purpose - /// @dev Staking contract address. - address internal _stakingContract; // Change type to address for testing purpose - /// @dev Staking vesting contract address. - address internal _stakingVestingContract; - - /// @dev The total of validators - uint256 public validatorCount; - /// @dev Mapping from validator index => validator address - mapping(uint256 => address) internal _validator; - /// @dev Mapping from validator address => bool - mapping(address => bool) internal _validatorMap; +import "../extensions/RONTransferHelper.sol"; +import "../extensions/HasStakingVestingContract.sol"; +import "../extensions/HasStakingContract.sol"; +import "../extensions/HasSlashIndicatorContract.sol"; +import "../interfaces/IRoninValidatorSet.sol"; +import "../libraries/Sorting.sol"; +import "../libraries/Math.sol"; +import "./CandidateManager.sol"; + +abstract contract RoninValidatorSet is + IRoninValidatorSet, + RONTransferHelper, + HasStakingContract, + HasStakingVestingContract, + HasSlashIndicatorContract, + CandidateManager, + Initializable +{ /// @dev The maximum number of validator. uint256 internal _maxValidatorNumber; - /// @dev The number of blocks in a epoch uint256 internal _numberOfBlocksInEpoch; /// @dev Returns the number of epochs in a period @@ -38,6 +31,13 @@ contract RoninValidatorSet is IRoninValidatorSet, RONTransferHelper, Initializab /// @dev The last updated block uint256 internal _lastUpdatedBlock; + /// @dev The total of validators + uint256 public validatorCount; + /// @dev Mapping from validator index => validator address + mapping(uint256 => address) internal _validator; + /// @dev Mapping from validator address => bool + mapping(address => bool) internal _validatorMap; + /// @dev Mapping from validator address => the last period that the validator has no pending reward mapping(address => mapping(uint256 => bool)) internal _rewardDeprecatedAtPeriod; /// @dev Mapping from validator address => the last block that the validator is jailed @@ -53,16 +53,6 @@ contract RoninValidatorSet is IRoninValidatorSet, RONTransferHelper, Initializab _; } - modifier onlySlashIndicatorContract() { - require(msg.sender == _slashIndicatorContract, "RoninValidatorSet: method caller must be slash indicator contract"); - _; - } - - modifier onlyGovernanceAdmin() { - require(msg.sender == _governanceAdmin, "RoninValidatorSet: method caller must be governance admin"); - _; - } - modifier whenEpochEnding() { require(epochEndingAt(block.number), "RoninValidatorSet: only allowed at the end of epoch"); _; @@ -84,7 +74,6 @@ contract RoninValidatorSet is IRoninValidatorSet, RONTransferHelper, Initializab * @dev Initializes the contract storage. */ function initialize( - address __governanceAdmin, address __slashIndicatorContract, address __stakingContract, address __stakingVestingContract, @@ -92,12 +81,9 @@ contract RoninValidatorSet is IRoninValidatorSet, RONTransferHelper, Initializab uint256 __numberOfBlocksInEpoch, uint256 __numberOfEpochsInPeriod ) external initializer { - _setGovernanceAdmin(__governanceAdmin); - - _slashIndicatorContract = __slashIndicatorContract; - _stakingContract = __stakingContract; - _stakingVestingContract = __stakingVestingContract; - + _setSlashIndicatorContract(__slashIndicatorContract); + _setStakingContract(__stakingContract); + _setStakingVestingContract(__stakingVestingContract); _setMaxValidatorNumber(__maxValidatorNumber); _setNumberOfBlocksInEpoch(__numberOfBlocksInEpoch); _setNumberOfEpochsInPeriod(__numberOfEpochsInPeriod); @@ -125,11 +111,11 @@ contract RoninValidatorSet is IRoninValidatorSet, RONTransferHelper, Initializab return; } - uint256 _bonusReward = IStakingVesting(_stakingVestingContract).requestBlockBonus(); + uint256 _bonusReward = _stakingVestingContract.requestBlockBonus(); uint256 _reward = _submittedReward + _bonusReward; IStaking _staking = IStaking(_stakingContract); - uint256 _rate = _staking.commissionRateOf(_coinbaseAddr); + uint256 _rate = 0; // _staking.commissionRateOf(_coinbaseAddr); uint256 _miningAmount = (_rate * _reward) / 100_00; uint256 _delegatingAmount = _reward - _miningAmount; @@ -167,7 +153,7 @@ contract RoninValidatorSet is IRoninValidatorSet, RONTransferHelper, Initializab uint256 _miningAmount = _miningReward[_validatorAddr]; delete _miningReward[_validatorAddr]; if (_miningAmount > 0) { - address payable _treasury = payable(_staking.treasuryAddressOf(_validatorAddr)); + address payable _treasury; // = payable(_staking.treasuryAddressOf(_validatorAddr)); require(_sendRON(_treasury, _miningAmount), "RoninValidatorSet: could not transfer RON treasury address"); emit MiningRewardDistributed(_validatorAddr, _miningAmount); } @@ -202,34 +188,6 @@ contract RoninValidatorSet is IRoninValidatorSet, RONTransferHelper, Initializab // FUNCTIONS FOR SLASH INDICATOR // /////////////////////////////////////////////////////////////////////////////////////// - /** - * @inheritdoc IRoninValidatorSet - */ - function governanceAdmin() external view override returns (address) { - return _governanceAdmin; - } - - /** - * @inheritdoc IRoninValidatorSet - */ - function slashIndicatorContract() external view override returns (address) { - return _slashIndicatorContract; - } - - /** - * @inheritdoc IRoninValidatorSet - */ - function stakingContract() external view override returns (address) { - return _stakingContract; - } - - /** - * @inheritdoc IRoninValidatorSet - */ - function stakingVestingContract() external view override returns (address) { - return _stakingVestingContract; - } - /** * @inheritdoc IRoninValidatorSet */ @@ -348,28 +306,21 @@ contract RoninValidatorSet is IRoninValidatorSet, RONTransferHelper, Initializab /** * @inheritdoc IRoninValidatorSet */ - function setGovernanceAdmin(address __governanceAdmin) external override onlyGovernanceAdmin { - _setGovernanceAdmin(__governanceAdmin); - } - - /** - * @inheritdoc IRoninValidatorSet - */ - function setMaxValidatorNumber(uint256 __maxValidatorNumber) external override onlyGovernanceAdmin { + function setMaxValidatorNumber(uint256 __maxValidatorNumber) external override onlyAdmin { _setMaxValidatorNumber(__maxValidatorNumber); } /** * @inheritdoc IRoninValidatorSet */ - function setNumberOfBlocksInEpoch(uint256 __numberOfBlocksInEpoch) external override onlyGovernanceAdmin { + function setNumberOfBlocksInEpoch(uint256 __numberOfBlocksInEpoch) external override onlyAdmin { _setNumberOfBlocksInEpoch(__numberOfBlocksInEpoch); } /** * @inheritdoc IRoninValidatorSet */ - function setNumberOfEpochsInPeriod(uint256 __numberOfEpochsInPeriod) external override onlyGovernanceAdmin { + function setNumberOfEpochsInPeriod(uint256 __numberOfEpochsInPeriod) external override onlyAdmin { _setNumberOfEpochsInPeriod(__numberOfEpochsInPeriod); } @@ -403,7 +354,8 @@ contract RoninValidatorSet is IRoninValidatorSet, RONTransferHelper, Initializab */ function _getValidatorCandidates() internal view returns (address[] memory _candidates) { uint256[] memory _weights; - (_candidates, _weights) = IStaking(_stakingContract).getCandidateWeights(); + // TODO: fix this. + // (_candidates, _weights) = IStaking(_stakingContract).getCandidateWeights(); // TODO: filter validators that do not have enough min balance uint256 _newLength = _candidates.length; for (uint256 _i; _i < _candidates.length; _i++) { @@ -457,42 +409,28 @@ contract RoninValidatorSet is IRoninValidatorSet, RONTransferHelper, Initializab emit ValidatorSetUpdated(_candidates); } - /** - * @dev Updates the address of governance admin - */ - function _setGovernanceAdmin(address __governanceAdmin) internal { - if (__governanceAdmin == _governanceAdmin) { - return; - } - - require(__governanceAdmin != address(0), "RoninValidatorSet: Cannot set admin to zero address"); - - _governanceAdmin = __governanceAdmin; - emit GovernanceAdminUpdated(__governanceAdmin); - } - /** * @dev Updates the max validator number */ - function _setMaxValidatorNumber(uint256 __maxValidatorNumber) internal { - _maxValidatorNumber = __maxValidatorNumber; - emit MaxValidatorNumberUpdated(__maxValidatorNumber); + function _setMaxValidatorNumber(uint256 _number) internal { + _maxValidatorNumber = _number; + emit MaxValidatorNumberUpdated(_number); } /** * @dev Updates the number of blocks in epoch */ - function _setNumberOfBlocksInEpoch(uint256 __numberOfBlocksInEpoch) internal { - _numberOfBlocksInEpoch = __numberOfBlocksInEpoch; - emit NumberOfBlocksInEpochUpdated(__numberOfBlocksInEpoch); + function _setNumberOfBlocksInEpoch(uint256 _number) internal { + _numberOfBlocksInEpoch = _number; + emit NumberOfBlocksInEpochUpdated(_number); } /** * @dev Updates the number of epochs in period */ - function _setNumberOfEpochsInPeriod(uint256 __numberOfEpochsInPeriod) internal { - _numberOfEpochsInPeriod = __numberOfEpochsInPeriod; - emit NumberOfEpochsInPeriodUpdated(__numberOfEpochsInPeriod); + function _setNumberOfEpochsInPeriod(uint256 _number) internal { + _numberOfEpochsInPeriod = _number; + emit NumberOfEpochsInPeriodUpdated(_number); } /** @@ -500,7 +438,7 @@ contract RoninValidatorSet is IRoninValidatorSet, RONTransferHelper, Initializab */ function _fallback() internal view { require( - msg.sender == _stakingVestingContract, + msg.sender == stakingVestingContract(), "RoninValidatorSet: only receives RON from staking vesting contract" ); } From 0966227abbdeea610a96baf0c30e6790636d836a Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 20 Sep 2022 18:32:22 +0700 Subject: [PATCH 147/190] Adopt changes for the rest --- contracts/SlashIndicator.sol | 71 +++------------- contracts/StakingVesting.sol | 28 ++----- contracts/interfaces/ICandidateManager.sol | 82 +++++++++++++++++++ contracts/interfaces/IRewardPool.sol | 7 +- contracts/interfaces/ISlashIndicator.sol | 23 ------ contracts/interfaces/IStaking.sol | 1 + contracts/interfaces/IStakingVesting.sol | 2 - .../MockRoninValidatorSetEpochSetter.sol | 2 +- contracts/mocks/MockSlashIndicator.sol | 4 - contracts/mocks/MockStaking.sol | 2 + contracts/mocks/MockValidatorSet.sol | 22 ++++- .../mocks/slash/MockValidatorSetForSlash.sol | 8 +- contracts/staking/RewardCalculation.sol | 10 +-- 13 files changed, 136 insertions(+), 126 deletions(-) create mode 100644 contracts/interfaces/ICandidateManager.sol diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index d21b948cb..a2e04ac57 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -4,9 +4,9 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "./interfaces/ISlashIndicator.sol"; -import "./interfaces/IRoninValidatorSet.sol"; +import "./extensions/HasValidatorContract.sol"; -contract SlashIndicator is ISlashIndicator, Initializable { +contract SlashIndicator is ISlashIndicator, HasValidatorContract, Initializable { /// @dev Mapping from validator address => unavailability indicator mapping(address => uint256) internal _unavailabilityIndicator; /// @dev The last block that a validator is slashed @@ -24,26 +24,11 @@ contract SlashIndicator is ISlashIndicator, Initializable { /// @dev The block duration to jail validator that reaches felony thresold. uint256 public felonyJailDuration; - /// @dev The validator contract - IRoninValidatorSet public validatorContract; - /// @dev The governance admin - address internal _governanceAdmin; - modifier onlyCoinbase() { require(msg.sender == block.coinbase, "SlashIndicator: method caller is not the coinbase"); _; } - modifier onlyValidatorContract() { - require(msg.sender == address(validatorContract), "SlashIndicator: method caller is not the validator contract"); - _; - } - - modifier onlyGovernanceAdmin() { - require(msg.sender == _governanceAdmin, "SlashIndicator: method caller is not the governance admin"); - _; - } - modifier oncePerBlock() { require( block.number > lastSlashedBlock, @@ -61,16 +46,14 @@ contract SlashIndicator is ISlashIndicator, Initializable { * @dev Initializes the contract storage. */ function initialize( - address __governanceAdmin, - IRoninValidatorSet _validatorSetContract, + address __validatorContract, uint256 _misdemeanorThreshold, uint256 _felonyThreshold, uint256 _slashFelonyAmount, uint256 _slashDoubleSignAmount, uint256 _felonyJailBlocks ) external initializer { - validatorContract = _validatorSetContract; - _setGovernanceAdmin(__governanceAdmin); + _setValidatorContract(__validatorContract); _setSlashThresholds(_felonyThreshold, _misdemeanorThreshold); _setSlashFelonyAmount(_slashFelonyAmount); _setSlashDoubleSignAmount(_slashDoubleSignAmount); @@ -94,10 +77,10 @@ contract SlashIndicator is ISlashIndicator, Initializable { // Slashes the validator as either the fenoly or the misdemeanor if (_count == felonyThreshold) { emit ValidatorSlashed(_validatorAddr, SlashType.FELONY); - validatorContract.slash(_validatorAddr, block.number + felonyJailDuration, slashFelonyAmount); + _validatorContract.slash(_validatorAddr, block.number + felonyJailDuration, slashFelonyAmount); } else if (_count == misdemeanorThreshold) { emit ValidatorSlashed(_validatorAddr, SlashType.MISDEMEANOR); - validatorContract.slash(_validatorAddr, 0, 0); + _validatorContract.slash(_validatorAddr, 0, 0); } } @@ -110,7 +93,7 @@ contract SlashIndicator is ISlashIndicator, Initializable { ) external override onlyCoinbase { bool _proved = false; // Proves the `_evidence` is right if (_proved) { - validatorContract.slash(_validatorAddr, type(uint256).max, slashDoubleSignAmount); + _validatorContract.slash(_validatorAddr, type(uint256).max, slashDoubleSignAmount); } } @@ -142,39 +125,28 @@ contract SlashIndicator is ISlashIndicator, Initializable { /** * @inheritdoc ISlashIndicator */ - function setGovernanceAdmin(address __governanceAdmin) external override onlyGovernanceAdmin { - _setGovernanceAdmin(__governanceAdmin); - } - - /** - * @inheritdoc ISlashIndicator - */ - function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) - external - override - onlyGovernanceAdmin - { + function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external override onlyAdmin { _setSlashThresholds(_felonyThreshold, _misdemeanorThreshold); } /** * @inheritdoc ISlashIndicator */ - function setSlashFelonyAmount(uint256 _slashFelonyAmount) external override onlyGovernanceAdmin { + function setSlashFelonyAmount(uint256 _slashFelonyAmount) external override onlyAdmin { _setSlashFelonyAmount(_slashFelonyAmount); } /** * @inheritdoc ISlashIndicator */ - function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external override onlyGovernanceAdmin { + function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external override onlyAdmin { _setSlashDoubleSignAmount(_slashDoubleSignAmount); } /** * @inheritdoc ISlashIndicator */ - function setFelonyJailDuration(uint256 _felonyJailDuration) external override onlyGovernanceAdmin { + function setFelonyJailDuration(uint256 _felonyJailDuration) external override onlyAdmin { _setFelonyJailDuration(_felonyJailDuration); } @@ -196,31 +168,10 @@ contract SlashIndicator is ISlashIndicator, Initializable { return (misdemeanorThreshold, felonyThreshold); } - /** - * @inheritdoc ISlashIndicator - */ - function governanceAdmin() external view override returns (address) { - return _governanceAdmin; - } - /////////////////////////////////////////////////////////////////////////////////////// // HELPER FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// - /** - * @dev Updates the address of governance admin - */ - function _setGovernanceAdmin(address __governanceAdmin) internal { - if (__governanceAdmin == _governanceAdmin) { - return; - } - - require(__governanceAdmin != address(0), "SlashIndicator: Cannot set admin to zero address"); - - _governanceAdmin = __governanceAdmin; - emit GovernanceAdminUpdated(__governanceAdmin); - } - /** * @dev Sets the slash thresholds */ diff --git a/contracts/StakingVesting.sol b/contracts/StakingVesting.sol index 16afef5ce..f0a5c9464 100644 --- a/contracts/StakingVesting.sol +++ b/contracts/StakingVesting.sol @@ -4,19 +4,14 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "./interfaces/IStakingVesting.sol"; +import "./extensions/HasValidatorContract.sol"; +import "./extensions/RONTransferHelper.sol"; -contract StakingVesting is Initializable, IStakingVesting { +contract StakingVesting is IStakingVesting, HasValidatorContract, RONTransferHelper, Initializable { /// @dev The block bonus whenever a new block is mined. uint256 internal _bonusPerBlock; /// @dev The last block number that the bonus reward sent. uint256 public lastBonusSentBlock; - /// @dev Validator contract address. - address internal _validatorContract; - - modifier onlyValidatorContract() { - require(msg.sender == _validatorContract, "Staking: method caller is not the validator contract"); - _; - } constructor() { _disableInitializers(); @@ -59,9 +54,9 @@ contract StakingVesting is Initializable, IStakingVesting { _amount = blockBonus(_block); if (_amount > 0) { - (bool _success, ) = payable(_validatorContract).call{ value: _amount }(""); - require(_success, "Staking: could not transfer RON to validator contract"); - emit BlockBonusTransferred(_block, _validatorContract, _amount); + address payable _validatorContractAddr = payable(validatorContract()); + require(_sendRON(_validatorContractAddr, _amount), "Staking: could not transfer RON to validator contract"); + emit BlockBonusTransferred(_block, _validatorContractAddr, _amount); } } @@ -75,15 +70,4 @@ contract StakingVesting is Initializable, IStakingVesting { _bonusPerBlock = _amount; emit BonusPerBlockUpdated(_amount); } - - /** - * @dev Sets the governance admin address. - * - * Emits the `ValidatorContractUpdated` event. - * - */ - function _setValidatorContract(address _newValidatorContract) internal { - _validatorContract = _newValidatorContract; - emit ValidatorContractUpdated(_newValidatorContract); - } } diff --git a/contracts/interfaces/ICandidateManager.sol b/contracts/interfaces/ICandidateManager.sol new file mode 100644 index 000000000..6dab0a0a7 --- /dev/null +++ b/contracts/interfaces/ICandidateManager.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface ICandidateManager { + struct ValidatorCandidate { + // Address of the validator that produces block, e.g. block.coinbase. This is so-called validator address. + address consensusAddr; + // Address that receives mining reward of the validator + address payable treasuryAddr; + // The percentile of reward that validators can be received, the rest goes to the delegators. + /// Values in range [0; 100_00] stands for 0-100% + uint256 commissionRate; + // Extra data + bytes extraData; + } + + /// @dev Emitted when the maximum number of validator candidates is updated. + event MaxValidatorCandidateUpdated(uint256 threshold); + /// @dev Emitted when the validator candidate is added. + event ValidatorCandidateAdded( + address indexed consensusAddr, + address indexed treasuryAddr, + uint256 indexed candidateIdx + ); + /// @dev Emitted when the validator candidate is removed. + event ValidatorCandidateRemoved(address indexed consensusAddr); + + /** + * @dev Returns the maximum number of validator candidate. + */ + function maxValidatorCandidate() external view returns (uint256); + + /** + * @dev Sets the maximum number of validator candidate. + * + * Requirements: + * - The method caller is governance admin. + * + * Emits the `MaxValidatorCandidateUpdated` event. + * + */ + function setMaxValidatorCandidate(uint256) external; + + /** + * @dev Adds a validator candidate. + * + * Requirements: + * - The method caller is staking contract. + * + * Emits the event `ValidatorCandidateAdded`. + * + */ + function addValidatorCandidate( + address _consensusAddr, + address payable _treasuryAddr, + uint256 _commissionRate + ) external; + + /** + * @dev Syncs the validator candidate list. Returns the total balance list of the new candidate list. + * + * Emits the event `ValidatorCandidateRemoved` when a candidate is removed. + * + */ + function syncCandidate() external returns (uint256[] memory _balances); + + /** + * @dev Returns whether the address is a validator (candidate). + */ + function isValidatorCandidate(address _addr) external view returns (bool); + + /** + * @dev Returns the validator candidate. + */ + function getValidatorCandidates() external view returns (address[] memory); + + /** + * @dev Returns candidates info. + */ + function getCandidateInfos() external view returns (ValidatorCandidate[] memory); +} diff --git a/contracts/interfaces/IRewardPool.sol b/contracts/interfaces/IRewardPool.sol index 224af5a02..314a890fb 100644 --- a/contracts/interfaces/IRewardPool.sol +++ b/contracts/interfaces/IRewardPool.sol @@ -53,7 +53,7 @@ interface IRewardPool { /** * @dev Returns total rewards from scratch including pending reward and claimable reward except the claimed amount. * - * @notice Do not use this function to get claimable reward, consider using the method `getClaimableReward` instead. + * Note: Do not use this function to get claimable reward, consider using the method `getClaimableReward` instead. * */ function getTotalReward(address _poolAddr, address _user) external view returns (uint256); @@ -77,4 +77,9 @@ interface IRewardPool { * @dev Returns the total staking amount of all users. */ function totalBalance(address _poolAddr) external view returns (uint256); + + /** + * @dev Returns the total staking amount of all users. + */ + function totalBalances(address[] calldata _poolAddr) external view returns (uint256[] memory); } diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index c9b631d2b..9ee2ac7ff 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -17,8 +17,6 @@ interface ISlashIndicator { event SlashDoubleSignAmountUpdated(uint256 slashDoubleSignAmount); /// @dev Emiited when the duration of jailing felony updated event FelonyJailDurationUpdated(uint256 felonyJailDuration); - /// @dev Emitted when the address of governance admin is updated. - event GovernanceAdminUpdated(address); enum SlashType { UNKNOWN, @@ -27,11 +25,6 @@ interface ISlashIndicator { DOUBLE_SIGNING } - /** - * @dev Returns the validator contract - */ - function validatorContract() external view returns (IRoninValidatorSet); - /** * @dev Slashes for unavailability by increasing the counter of validator with `_valAddr`. * If the counter passes the threshold, call the function from the validator contract. @@ -108,17 +101,6 @@ interface ISlashIndicator { */ function setFelonyJailDuration(uint256 _felonyJailDuration) external; - /** - * @dev Updates the governance admin - * - * Requirements: - * - The method caller is the governance admin - * - * Emits the event `GovernanceAdminUpdated` - * - */ - function setGovernanceAdmin(address _governanceAdmin) external; - /** * @dev Gets slash indicator of a validator */ @@ -128,9 +110,4 @@ interface ISlashIndicator { * @dev Gets the slash thresholds */ function getSlashThresholds() external view returns (uint256 misdemeanorThreshold, uint256 felonyThreshold); - - /** - * @dev Returns the governance admin address - */ - function governanceAdmin() external view returns (address); } diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index edc2cbffe..de11bf061 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -17,6 +17,7 @@ interface IStaking is IRewardPool { // Mapping from delegator => delegated amount mapping(address => uint256) delegatedAmount; } + /// @dev Emitted when the validator candidate requested to renounce. event ValidatorRenounceRequested(address indexed consensusAddr, uint256 amount); /// @dev Emitted when the renounce request is finalized. diff --git a/contracts/interfaces/IStakingVesting.sol b/contracts/interfaces/IStakingVesting.sol index 55157719a..449fe136f 100644 --- a/contracts/interfaces/IStakingVesting.sol +++ b/contracts/interfaces/IStakingVesting.sol @@ -7,8 +7,6 @@ interface IStakingVesting { event BlockBonusTransferred(uint256 indexed blockNumber, address indexed recipient, uint256 amount); /// @dev Emitted when the block bonus is updated event BonusPerBlockUpdated(uint256); - /// @dev Emitted when the address of validator contract is updated. - event ValidatorContractUpdated(address); /** * @dev Returns the bonus amount for the block `_block`. diff --git a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol index ff922eca9..4ff13c605 100644 --- a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol +++ b/contracts/mocks/MockRoninValidatorSetEpochSetter.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.9; -import "../RoninValidatorSet.sol"; +import "../validator/RoninValidatorSet.sol"; contract MockRoninValidatorSetEpochSetter is RoninValidatorSet { uint256[] internal _epochs; diff --git a/contracts/mocks/MockSlashIndicator.sol b/contracts/mocks/MockSlashIndicator.sol index bb764c950..a5d68a465 100644 --- a/contracts/mocks/MockSlashIndicator.sol +++ b/contracts/mocks/MockSlashIndicator.sol @@ -50,10 +50,6 @@ contract MockSlashIndicator is ISlashIndicator { function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external override {} - function governanceAdmin() external view override returns (address) {} - - function setGovernanceAdmin(address __newAddr) external override {} - function setSlashFelonyAmount(uint256 _slashFelonyAmount) external override {} function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external override {} diff --git a/contracts/mocks/MockStaking.sol b/contracts/mocks/MockStaking.sol index 9234ce885..54c51fef9 100644 --- a/contracts/mocks/MockStaking.sol +++ b/contracts/mocks/MockStaking.sol @@ -84,4 +84,6 @@ contract MockStaking is RewardCalculation { } } } + + function totalBalances(address[] calldata _poolAddr) external view override returns (uint256[] memory) {} } diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index 8c2cb5a51..0c50c9752 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -70,8 +70,6 @@ contract MockValidatorSet is IRoninValidatorSet { function getLastUpdatedBlock() external view override returns (uint256) {} - function governanceAdmin() external view override returns (address) {} - function jailed(address[] memory) external view override returns (bool[] memory) {} function rewardDeprecated(address[] memory, uint256 _period) external view override returns (bool[] memory) {} @@ -94,8 +92,6 @@ contract MockValidatorSet is IRoninValidatorSet { ISlashIndicator(slashIndicatorContract).resetCounters(_validatorAddrs); } - function setGovernanceAdmin(address _governanceAdmin) external override {} - function setMaxValidatorNumber(uint256 _maxValidatorNumber) external override {} function setNumberOfBlocksInEpoch(uint256 _numberOfBlocksInEpoch) external override {} @@ -103,4 +99,22 @@ contract MockValidatorSet is IRoninValidatorSet { function setNumberOfEpochsInPeriod(uint256 _numberOfEpochsInPeriod) external override {} function maxValidatorNumber() external view override returns (uint256 _maximumValidatorNumber) {} + + function maxValidatorCandidate() external view override returns (uint256) {} + + function setMaxValidatorCandidate(uint256) external override {} + + function addValidatorCandidate( + address _consensusAddr, + address payable _treasuryAddr, + uint256 _commissionRate + ) external override {} + + function syncCandidate() external override returns (uint256[] memory _balances) {} + + function isValidatorCandidate(address _addr) external view override returns (bool) {} + + function getValidatorCandidates() external view override returns (address[] memory) {} + + function getCandidateInfos() external view override returns (ValidatorCandidate[] memory) {} } diff --git a/contracts/mocks/slash/MockValidatorSetForSlash.sol b/contracts/mocks/slash/MockValidatorSetForSlash.sol index 811378d4e..b8de57779 100644 --- a/contracts/mocks/slash/MockValidatorSetForSlash.sol +++ b/contracts/mocks/slash/MockValidatorSetForSlash.sol @@ -5,14 +5,14 @@ pragma solidity ^0.8.9; import "../../interfaces/ISlashIndicator.sol"; contract MockValidatorSetForSlash { - ISlashIndicator private __slashingContract; + ISlashIndicator private _slashingContract; function _setSlashingContract() internal view virtual returns (ISlashIndicator) { - return __slashingContract; + return _slashingContract; } function setSlashingContract(ISlashIndicator _addr) external { - __slashingContract = _addr; + _slashingContract = _addr; } function slash( @@ -22,6 +22,6 @@ contract MockValidatorSetForSlash { ) external {} function resetCounters(address[] calldata _addr) external { - __slashingContract.resetCounters(_addr); + _slashingContract.resetCounters(_addr); } } diff --git a/contracts/staking/RewardCalculation.sol b/contracts/staking/RewardCalculation.sol index fc80c1789..67c43a1f0 100644 --- a/contracts/staking/RewardCalculation.sol +++ b/contracts/staking/RewardCalculation.sol @@ -76,7 +76,7 @@ abstract contract RewardCalculation is IRewardPool { * Emits the `SettledRewardUpdated` event if the last block user made changes is recorded in the settled period. * Emits the `PendingRewardUpdated` event. * - * @notice The method should be called whenever the user's balance changes. + * Note: The method should be called whenever the user's balance changes. * */ function _syncUserReward( @@ -114,7 +114,7 @@ abstract contract RewardCalculation is IRewardPool { * * Emits the `PendingRewardUpdated` event and the `SettledRewardUpdated` event. * - * @notice This method should be called before transferring rewards for the user. + * Note: This method should be called before transferring rewards for the user. * */ function _claimReward(address _poolAddr, address _user) internal returns (uint256 _amount) { @@ -142,7 +142,7 @@ abstract contract RewardCalculation is IRewardPool { * * Emits the `PendingPoolUpdated` event. * - * @notice This method should not be called after the pending pool is sinked. + * Note: This method should not be called after the pending pool is sinked. * */ function _recordReward(address _poolAddr, uint256 _reward) internal { @@ -157,7 +157,7 @@ abstract contract RewardCalculation is IRewardPool { * * Emits the `PendingPoolUpdated` event. * - * @notice This method should be called when the pool is sinked. + * Note: This method should be called when the pool is sinked. * */ function _sinkPendingReward(address _poolAddr) internal { @@ -172,7 +172,7 @@ abstract contract RewardCalculation is IRewardPool { * * Emits the `SettledPoolsUpdated` event. * - * @notice This method should be called once in the end of each period. + * Note: This method should be called once in the end of each period. * */ function _onPoolsSettled(address[] calldata _poolList) internal { From 035d327376c59345dd8e444b24ebaed7d43cf766 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 20 Sep 2022 18:38:53 +0700 Subject: [PATCH 148/190] Work around for proxy admin compile --- contracts/mocks/sorting/MockSorting.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/mocks/sorting/MockSorting.sol b/contracts/mocks/sorting/MockSorting.sol index 8bed8ab22..98a700c2a 100644 --- a/contracts/mocks/sorting/MockSorting.sol +++ b/contracts/mocks/sorting/MockSorting.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.9; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import "../../libraries/Sorting.sol"; contract MockSorting { From c93051c24540c3dd513862ff8c008b6ba87225ba Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 20 Sep 2022 18:53:52 +0700 Subject: [PATCH 149/190] [Validator] Adopt candidates storage --- contracts/SlashIndicator.sol | 4 + contracts/interfaces/ICandidateManager.sol | 2 +- contracts/mocks/MockValidatorSet.sol | 2 +- contracts/validator/CandidateManager.sol | 4 +- contracts/validator/RoninValidatorSet.sol | 89 ++++++++++++---------- 5 files changed, 57 insertions(+), 44 deletions(-) diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index a2e04ac57..09fdd3500 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -78,6 +78,10 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, Initializable if (_count == felonyThreshold) { emit ValidatorSlashed(_validatorAddr, SlashType.FELONY); _validatorContract.slash(_validatorAddr, block.number + felonyJailDuration, slashFelonyAmount); + delete _unavailabilityIndicator[_validatorAddr]; + address[] memory _addrList = new address[](1); + _addrList[0] = _validatorAddr; + emit UnavailabilityIndicatorsReset(_addrList); } else if (_count == misdemeanorThreshold) { emit ValidatorSlashed(_validatorAddr, SlashType.MISDEMEANOR); _validatorContract.slash(_validatorAddr, 0, 0); diff --git a/contracts/interfaces/ICandidateManager.sol b/contracts/interfaces/ICandidateManager.sol index 6dab0a0a7..c7edbfe40 100644 --- a/contracts/interfaces/ICandidateManager.sol +++ b/contracts/interfaces/ICandidateManager.sol @@ -63,7 +63,7 @@ interface ICandidateManager { * Emits the event `ValidatorCandidateRemoved` when a candidate is removed. * */ - function syncCandidate() external returns (uint256[] memory _balances); + function syncCandidates() external returns (uint256[] memory _balances); /** * @dev Returns whether the address is a validator (candidate). diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index 0c50c9752..6fc11ded0 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -110,7 +110,7 @@ contract MockValidatorSet is IRoninValidatorSet { uint256 _commissionRate ) external override {} - function syncCandidate() external override returns (uint256[] memory _balances) {} + function syncCandidates() external override returns (uint256[] memory _balances) {} function isValidatorCandidate(address _addr) external view override returns (bool) {} diff --git a/contracts/validator/CandidateManager.sol b/contracts/validator/CandidateManager.sol index ac35eb7b3..b4f8f22c4 100644 --- a/contracts/validator/CandidateManager.sol +++ b/contracts/validator/CandidateManager.sol @@ -53,7 +53,7 @@ contract CandidateManager is ICandidateManager, HasStakingContract { /** * @inheritdoc ICandidateManager */ - function syncCandidate() public override returns (uint256[] memory _balances) { + function syncCandidates() public override returns (uint256[] memory _balances) { IStaking _staking = _stakingContract; uint256 _minBalance = _staking.minValidatorBalance(); _balances = _staking.totalBalances(_candidates); @@ -91,7 +91,7 @@ contract CandidateManager is ICandidateManager, HasStakingContract { /** * @inheritdoc ICandidateManager */ - function getValidatorCandidates() external view override returns (address[] memory) { + function getValidatorCandidates() public view override returns (address[] memory) { return _candidates; } diff --git a/contracts/validator/RoninValidatorSet.sol b/contracts/validator/RoninValidatorSet.sol index a170bf4ad..2d94968ca 100644 --- a/contracts/validator/RoninValidatorSet.sol +++ b/contracts/validator/RoninValidatorSet.sol @@ -164,14 +164,16 @@ abstract contract RoninValidatorSet is } if (_periodEnding) { - // TODO: reset for candidates / kicked validators ISlashIndicator(_slashIndicatorContract).resetCounters(_validators); - } - _staking.settleRewardPools(_validators); - if (_delegatingAmount > 0) { - require(_sendRON(payable(address(_staking)), 0), "RoninValidatorSet: could not transfer RON to staking contract"); - emit StakingRewardDistributed(_delegatingAmount); + _staking.settleRewardPools(_validators); + if (_delegatingAmount > 0) { + require( + _sendRON(payable(address(_staking)), 0), + "RoninValidatorSet: could not transfer RON to staking contract" + ); + emit StakingRewardDistributed(_delegatingAmount); + } } _updateValidatorSet(); @@ -328,47 +330,25 @@ abstract contract RoninValidatorSet is // HELPER FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// - /** - * @dev Returns whether the reward of the validator is put in jail (cannot join the set of validators) during the current period. - */ - function _jailed(address _validatorAddr) internal view returns (bool) { - return block.number <= _jailedUntil[_validatorAddr]; - } - - /** - * @dev Returns whether the validator has no pending reward in that period. - */ - function _rewardDeprecated(address _validatorAddr, uint256 _period) internal view returns (bool) { - return _rewardDeprecatedAtPeriod[_validatorAddr][_period]; - } - - /** - * @dev Returns whether the address `_addr` is validator or not. - */ - function _isValidator(address _addr) internal view returns (bool) { - return _validatorMap[_addr]; - } - /** * @dev Returns validator candidates list. */ - function _getValidatorCandidates() internal view returns (address[] memory _candidates) { - uint256[] memory _weights; - // TODO: fix this. - // (_candidates, _weights) = IStaking(_stakingContract).getCandidateWeights(); - // TODO: filter validators that do not have enough min balance - uint256 _newLength = _candidates.length; + function _syncNewValidatorSet() internal returns (address[] memory _candidates) { + uint256[] memory _weights = syncCandidates(); + _candidates = _candidates; + + uint256 _length = _candidates.length; for (uint256 _i; _i < _candidates.length; _i++) { if (_jailed(_candidates[_i])) { - _newLength--; - _candidates[_i] = _candidates[_newLength]; - _weights[_i] = _weights[_newLength]; + _length--; + _candidates[_i] = _candidates[_length]; + _weights[_i] = _weights[_length]; } } assembly { - mstore(_candidates, _newLength) - mstore(_weights, _newLength) + mstore(_candidates, _length) + mstore(_weights, _length) } _candidates = Sorting.sort(_candidates, _weights); @@ -382,8 +362,7 @@ abstract contract RoninValidatorSet is * */ function _updateValidatorSet() internal virtual { - address[] memory _candidates = _getValidatorCandidates(); - + address[] memory _candidates = _syncNewValidatorSet(); uint256 _newValidatorCount = Math.min(_maxValidatorNumber, _candidates.length); assembly { @@ -409,8 +388,32 @@ abstract contract RoninValidatorSet is emit ValidatorSetUpdated(_candidates); } + /** + * @dev Returns whether the reward of the validator is put in jail (cannot join the set of validators) during the current period. + */ + function _jailed(address _validatorAddr) internal view returns (bool) { + return block.number <= _jailedUntil[_validatorAddr]; + } + + /** + * @dev Returns whether the validator has no pending reward in that period. + */ + function _rewardDeprecated(address _validatorAddr, uint256 _period) internal view returns (bool) { + return _rewardDeprecatedAtPeriod[_validatorAddr][_period]; + } + + /** + * @dev Returns whether the address `_addr` is validator or not. + */ + function _isValidator(address _addr) internal view returns (bool) { + return _validatorMap[_addr]; + } + /** * @dev Updates the max validator number + * + * Emits the event `MaxValidatorNumberUpdated` + * */ function _setMaxValidatorNumber(uint256 _number) internal { _maxValidatorNumber = _number; @@ -419,6 +422,9 @@ abstract contract RoninValidatorSet is /** * @dev Updates the number of blocks in epoch + * + * Emits the event `NumberOfBlocksInEpochUpdated` + * */ function _setNumberOfBlocksInEpoch(uint256 _number) internal { _numberOfBlocksInEpoch = _number; @@ -427,6 +433,9 @@ abstract contract RoninValidatorSet is /** * @dev Updates the number of epochs in period + * + * Emits the event `NumberOfEpochsInPeriodUpdated` + * */ function _setNumberOfEpochsInPeriod(uint256 _number) internal { _numberOfEpochsInPeriod = _number; From edd0b7becd74bfcc9ded2f3cc38e89ec96fe9eb7 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 21 Sep 2022 08:44:29 +0700 Subject: [PATCH 150/190] Fix test compile --- contracts/StakingVesting.sol | 4 --- contracts/validator/RoninValidatorSet.sol | 8 +++--- src/config.ts | 4 +-- src/deploy/calculate-address.ts | 1 + src/deploy/proxy/ronin-validator-proxy.ts | 3 ++- src/deploy/proxy/slash-indicator-proxy.ts | 2 +- src/deploy/proxy/staking-proxy.ts | 3 +-- src/deploy/proxy/staking-vesting-proxy.ts | 1 + src/deploy/verify-address.ts | 3 ++- .../integration/ActionSlashValidators.test.ts | 2 +- test/integration/ActionSubmitReward.test.ts | 2 +- test/integration/ActionWrapUpEpoch.test.ts | 2 +- test/integration/Configuration.test.ts | 27 +++++-------------- test/slash/SlashIndicator.test.ts | 4 +-- test/staking/Staking.test.ts | 12 +++------ test/validator/RoninValidatorSet.test.ts | 11 +++----- 16 files changed, 31 insertions(+), 58 deletions(-) diff --git a/contracts/StakingVesting.sol b/contracts/StakingVesting.sol index f0a5c9464..fef877826 100644 --- a/contracts/StakingVesting.sol +++ b/contracts/StakingVesting.sol @@ -17,10 +17,6 @@ contract StakingVesting is IStakingVesting, HasValidatorContract, RONTransferHel _disableInitializers(); } - receive() external payable onlyValidatorContract {} - - fallback() external payable onlyValidatorContract {} - /** * @dev Initializes the contract storage. */ diff --git a/contracts/validator/RoninValidatorSet.sol b/contracts/validator/RoninValidatorSet.sol index 2d94968ca..92bdeab03 100644 --- a/contracts/validator/RoninValidatorSet.sol +++ b/contracts/validator/RoninValidatorSet.sol @@ -13,7 +13,7 @@ import "../libraries/Sorting.sol"; import "../libraries/Math.sol"; import "./CandidateManager.sol"; -abstract contract RoninValidatorSet is +contract RoninValidatorSet is IRoninValidatorSet, RONTransferHelper, HasStakingContract, @@ -78,6 +78,7 @@ abstract contract RoninValidatorSet is address __stakingContract, address __stakingVestingContract, uint256 __maxValidatorNumber, + uint256 __maxValidatorCandidate, uint256 __numberOfBlocksInEpoch, uint256 __numberOfEpochsInPeriod ) external initializer { @@ -85,6 +86,7 @@ abstract contract RoninValidatorSet is _setStakingContract(__stakingContract); _setStakingVestingContract(__stakingVestingContract); _setMaxValidatorNumber(__maxValidatorNumber); + _setMaxValidatorCandidate(__maxValidatorCandidate); _setNumberOfBlocksInEpoch(__numberOfBlocksInEpoch); _setNumberOfEpochsInPeriod(__numberOfEpochsInPeriod); } @@ -115,7 +117,7 @@ abstract contract RoninValidatorSet is uint256 _reward = _submittedReward + _bonusReward; IStaking _staking = IStaking(_stakingContract); - uint256 _rate = 0; // _staking.commissionRateOf(_coinbaseAddr); + uint256 _rate = _candidateInfo[_coinbaseAddr].commissionRate; uint256 _miningAmount = (_rate * _reward) / 100_00; uint256 _delegatingAmount = _reward - _miningAmount; @@ -153,7 +155,7 @@ abstract contract RoninValidatorSet is uint256 _miningAmount = _miningReward[_validatorAddr]; delete _miningReward[_validatorAddr]; if (_miningAmount > 0) { - address payable _treasury; // = payable(_staking.treasuryAddressOf(_validatorAddr)); + address payable _treasury = _candidateInfo[_validatorAddr].treasuryAddr; require(_sendRON(_treasury, _miningAmount), "RoninValidatorSet: could not transfer RON treasury address"); emit MiningRewardDistributed(_validatorAddr, _miningAmount); } diff --git a/src/config.ts b/src/config.ts index d93951dd6..3d9873e3b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -25,7 +25,6 @@ export interface InitAddr { export interface StakingConf { [network: LiteralNetwork]: | { - maxValidatorCandidate: BigNumberish; minValidatorBalance: BigNumberish; } | undefined; @@ -56,6 +55,7 @@ export interface RoninValidatorSetConf { [network: LiteralNetwork]: | { maxValidatorNumber: BigNumberish; + maxValidatorCandidate: BigNumberish; numberOfBlocksInEpoch: BigNumberish; numberOfEpochsInPeriod: BigNumberish; } @@ -73,7 +73,6 @@ export const stakingConfig: StakingConf = { [Network.Hardhat]: undefined, [Network.Devnet]: { minValidatorBalance: BigNumber.from(10).pow(18).mul(BigNumber.from(10).pow(5)), // 100.000 RON - maxValidatorCandidate: 100, }, [Network.Testnet]: undefined, [Network.Mainnet]: undefined, @@ -109,6 +108,7 @@ export const roninValidatorSetConf: RoninValidatorSetConf = { [Network.Hardhat]: undefined, [Network.Devnet]: { maxValidatorNumber: 21, + maxValidatorCandidate: 100, numberOfBlocksInEpoch: 600, numberOfEpochsInPeriod: 48, // 1 day }, diff --git a/src/deploy/calculate-address.ts b/src/deploy/calculate-address.ts index 292f8bb3d..3ed087ab0 100644 --- a/src/deploy/calculate-address.ts +++ b/src/deploy/calculate-address.ts @@ -1,5 +1,6 @@ import { ethers, network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; + import { initAddress } from '../config'; const deploy = async ({ getNamedAccounts }: HardhatRuntimeEnvironment) => { diff --git a/src/deploy/proxy/ronin-validator-proxy.ts b/src/deploy/proxy/ronin-validator-proxy.ts index c2440e4ad..d9738cd65 100644 --- a/src/deploy/proxy/ronin-validator-proxy.ts +++ b/src/deploy/proxy/ronin-validator-proxy.ts @@ -1,5 +1,6 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; + import { roninValidatorSetConf, initAddress } from '../../config'; import { RoninValidatorSet__factory } from '../../types'; @@ -11,11 +12,11 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const logicContract = await deployments.get('RoninValidatorSetLogic'); const data = new RoninValidatorSet__factory().interface.encodeFunctionData('initialize', [ - initAddress[network.name]!.governanceAdmin, initAddress[network.name]!.slashIndicator, initAddress[network.name]!.stakingContract, initAddress[network.name]!.stakingVestingContract, roninValidatorSetConf[network.name]!.maxValidatorNumber, + roninValidatorSetConf[network.name]!.maxValidatorCandidate, roninValidatorSetConf[network.name]!.numberOfBlocksInEpoch, roninValidatorSetConf[network.name]!.numberOfEpochsInPeriod, ]); diff --git a/src/deploy/proxy/slash-indicator-proxy.ts b/src/deploy/proxy/slash-indicator-proxy.ts index e65819853..24d022dec 100644 --- a/src/deploy/proxy/slash-indicator-proxy.ts +++ b/src/deploy/proxy/slash-indicator-proxy.ts @@ -1,5 +1,6 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; + import { slashIndicatorConf, initAddress } from '../../config'; import { SlashIndicator__factory } from '../../types'; @@ -11,7 +12,6 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const logicContract = await deployments.get('SlashIndicatorLogic'); const data = new SlashIndicator__factory().interface.encodeFunctionData('initialize', [ - initAddress[network.name]!.governanceAdmin, initAddress[network.name]!.validatorContract, slashIndicatorConf[network.name]!.misdemeanorThreshold, slashIndicatorConf[network.name]!.felonyThreshold, diff --git a/src/deploy/proxy/staking-proxy.ts b/src/deploy/proxy/staking-proxy.ts index 6dc85ea53..da091bcc0 100644 --- a/src/deploy/proxy/staking-proxy.ts +++ b/src/deploy/proxy/staking-proxy.ts @@ -1,5 +1,6 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; + import { stakingConfig, initAddress } from '../../config'; import { Staking__factory } from '../../types'; @@ -12,8 +13,6 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const data = new Staking__factory().interface.encodeFunctionData('initialize', [ initAddress[network.name]!.validatorContract, - initAddress[network.name]!.governanceAdmin, - stakingConfig[network.name]!.maxValidatorCandidate, stakingConfig[network.name]!.minValidatorBalance, ]); diff --git a/src/deploy/proxy/staking-vesting-proxy.ts b/src/deploy/proxy/staking-vesting-proxy.ts index 34b8f9f05..300a44d34 100644 --- a/src/deploy/proxy/staking-vesting-proxy.ts +++ b/src/deploy/proxy/staking-vesting-proxy.ts @@ -1,6 +1,7 @@ import { BigNumber } from 'ethers'; import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; + import { initAddress, stakingVestingConfig } from '../../config'; import { StakingVesting__factory } from '../../types'; diff --git a/src/deploy/verify-address.ts b/src/deploy/verify-address.ts index bdea941c4..1ae4b0742 100644 --- a/src/deploy/verify-address.ts +++ b/src/deploy/verify-address.ts @@ -1,5 +1,6 @@ -import { ethers, network } from 'hardhat'; +import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; + import { initAddress } from '../config'; const deploy = async ({ deployments }: HardhatRuntimeEnvironment) => { diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index b4d65f4bf..d83ab0548 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -68,12 +68,12 @@ describe('[Integration] Slash validators', () => { }; roninValidatorSetConf[network.name] = { maxValidatorNumber: maxValidatorNumber, + maxValidatorCandidate: maxValidatorCandidate, numberOfBlocksInEpoch: numberOfBlocksInEpoch, numberOfEpochsInPeriod: numberOfEpochsInPeriod, }; stakingConfig[network.name] = { minValidatorBalance: minValidatorBalance, - maxValidatorCandidate: maxValidatorCandidate, }; stakingVestingConfig[network.name] = { bonusPerBlock: bonusPerBlock, diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index 8e5fd43f9..754636ebc 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -68,12 +68,12 @@ describe('[Integration] Submit Block Reward', () => { }; roninValidatorSetConf[network.name] = { maxValidatorNumber: maxValidatorNumber, + maxValidatorCandidate: maxValidatorCandidate, numberOfBlocksInEpoch: numberOfBlocksInEpoch, numberOfEpochsInPeriod: numberOfEpochsInPeriod, }; stakingConfig[network.name] = { minValidatorBalance: minValidatorBalance, - maxValidatorCandidate: maxValidatorCandidate, }; stakingVestingConfig[network.name] = { bonusPerBlock: bonusPerBlock, diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index 3468ca891..7b9e18e1d 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -70,12 +70,12 @@ describe('[Integration] Wrap up epoch', () => { }; roninValidatorSetConf[network.name] = { maxValidatorNumber: maxValidatorNumber, + maxValidatorCandidate: maxValidatorCandidate, numberOfBlocksInEpoch: numberOfBlocksInEpoch, numberOfEpochsInPeriod: numberOfEpochsInPeriod, }; stakingConfig[network.name] = { minValidatorBalance: minValidatorBalance, - maxValidatorCandidate: maxValidatorCandidate, }; stakingVestingConfig[network.name] = { bonusPerBlock: bonusPerBlock, diff --git a/test/integration/Configuration.test.ts b/test/integration/Configuration.test.ts index e242596a2..92c05b9ff 100644 --- a/test/integration/Configuration.test.ts +++ b/test/integration/Configuration.test.ts @@ -59,12 +59,12 @@ describe('[Integration] Configuration check', () => { }; roninValidatorSetConf[network.name] = { maxValidatorNumber: maxValidatorNumber, + maxValidatorCandidate: maxValidatorNumber, numberOfBlocksInEpoch: numberOfBlocksInEpoch, numberOfEpochsInPeriod: numberOfEpochsInPeriod, }; stakingConfig[network.name] = { minValidatorBalance: minValidatorBalance, - maxValidatorCandidate: maxValidatorNumber, }; stakingVestingConfig[network.name] = { bonusPerBlock: bonusPerBlock, @@ -85,11 +85,6 @@ describe('[Integration] Configuration check', () => { }); describe('ValidatorSetContract configuration', async () => { - it('Should config the governanceAdmin correctly', async () => { - let _governanceAdmin = await validatorContract.governanceAdmin(); - expect(_governanceAdmin).to.eq(governanceAdmin.address); - }); - it('Should the ValidatorSetContract config the StakingContract correctly', async () => { let _stakingContract = await validatorContract.stakingContract(); expect(_stakingContract).to.eq(stakingContract.address); @@ -105,6 +100,11 @@ describe('[Integration] Configuration check', () => { expect(_maxValidatorNumber).to.eq(maxValidatorNumber); }); + it('Should config the maxValidatorCandidate correctly', async () => { + let _maxValidatorCandidate = await validatorContract.maxValidatorCandidate(); + expect(_maxValidatorCandidate).to.eq(maxValidatorNumber); + }); + it('Should config the numberOfBlocksInEpoch correctly', async () => { let _numberOfBlocksInEpoch = await validatorContract.numberOfBlocksInEpoch(); expect(_numberOfBlocksInEpoch).to.eq(numberOfBlocksInEpoch); @@ -117,11 +117,6 @@ describe('[Integration] Configuration check', () => { }); describe('StakingContract configuration', async () => { - it('Should config the governanceAdmin correctly', async () => { - let _governanceAdmin = await stakingContract.governanceAdmin(); - expect(_governanceAdmin).to.eq(governanceAdmin.address); - }); - it('Should the StakingContract config the ValidatorSetContract correctly', async () => { let _validatorSetContract = await stakingContract.validatorContract(); expect(_validatorSetContract).to.eq(validatorContract.address); @@ -131,19 +126,9 @@ describe('[Integration] Configuration check', () => { let _minValidatorBalance = await stakingContract.minValidatorBalance(); expect(_minValidatorBalance).to.eq(minValidatorBalance); }); - - it('Should config the maxValidatorCandidate correctly', async () => { - let _maxValidatorCandidate = await stakingContract.maxValidatorCandidate(); - expect(_maxValidatorCandidate).to.eq(maxValidatorNumber); - }); }); describe('SlashIndicatorContract configuration', async () => { - it('Should config the governanceAdmin correctly', async () => { - let _governanceAdmin = await slashContract.governanceAdmin(); - expect(_governanceAdmin).to.eq(governanceAdmin.address); - }); - it('Should the SlashIndicatorContract config the ValidatorSetContract correctly', async () => { let _validatorSetContract = await slashContract.validatorContract(); expect(_validatorSetContract).to.eq(validatorContract.address); diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index 2ee1ab51b..c070f354c 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -21,7 +21,6 @@ let deployer: SignerWithAddress; let proxyAdmin: SignerWithAddress; let mockValidatorsContract: MockValidatorSetForSlash; let vagabond: SignerWithAddress; -let governanceAdmin: SignerWithAddress; let coinbases: SignerWithAddress[]; let defaultCoinbase: Address; let localIndicators: number[]; @@ -56,7 +55,7 @@ describe('Slash indicator test', () => { let misdemeanorThreshold: number; before(async () => { - [deployer, proxyAdmin, vagabond, governanceAdmin, ...coinbases] = await ethers.getSigners(); + [deployer, proxyAdmin, vagabond, ...coinbases] = await ethers.getSigners(); localIndicators = Array(coinbases.length).fill(0); defaultCoinbase = await network.provider.send('eth_coinbase'); @@ -76,7 +75,6 @@ describe('Slash indicator test', () => { logicContract.address, proxyAdmin.address, logicContract.interface.encodeFunctionData('initialize', [ - governanceAdmin.address, mockValidatorsContract.address, slashIndicatorConf[network.name]!.misdemeanorThreshold, slashIndicatorConf[network.name]!.felonyThreshold, diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index 29cf58b5d..fce61e451 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -16,7 +16,6 @@ let deployer: SignerWithAddress; let proxyAdmin: SignerWithAddress; let userA: SignerWithAddress; let userB: SignerWithAddress; -let governanceAdmin: SignerWithAddress; let validatorContract: MockValidatorSet; let stakingContract: Staking; let validatorCandidates: SignerWithAddress[]; @@ -92,7 +91,7 @@ const minValidatorBalance = BigNumber.from(2); describe('Staking test', () => { before(async () => { - [deployer, proxyAdmin, userA, userB, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + [deployer, proxyAdmin, userA, userB, ...validatorCandidates] = await ethers.getSigners(); validatorCandidates = validatorCandidates.slice(0, 3); const stakingVestingContract = await new StakingVesting__factory(deployer).deploy(); const nonce = await deployer.getTransactionCount(); @@ -110,12 +109,7 @@ describe('Staking test', () => { const proxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( logicContract.address, proxyAdmin.address, - logicContract.interface.encodeFunctionData('initialize', [ - validatorContract.address, - governanceAdmin.address, - 50, - minValidatorBalance, - ]) + logicContract.interface.encodeFunctionData('initialize', [validatorContract.address, minValidatorBalance]) ); await proxyContract.deployed(); stakingContract = Staking__factory.connect(proxyContract.address, deployer); @@ -223,7 +217,7 @@ describe('Staking test', () => { describe('Reward Calculation test', () => { before(async () => { poolAddr = validatorCandidates[0]; - await stakingContract.connect(governanceAdmin).setMinValidatorBalance(0); + await stakingContract.connect(proxyAdmin).setMinValidatorBalance(0); await stakingContract.connect(poolAddr).proposeValidator(poolAddr.address, poolAddr.address, 0, { value: 0 }); await network.provider.send('evm_setAutomine', [false]); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index af7bd201e..acac022e9 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -86,11 +86,11 @@ describe('Ronin Validator Set test', () => { validatorLogicContract.address, proxyAdmin.address, validatorLogicContract.interface.encodeFunctionData('initialize', [ - governanceAdmin.address, slashIndicator.address, stakingContractAddr, stakingVesting.address, maxValidatorNumber, + maxValidatorCandidate, numberOfBlocksInEpoch, numberOfEpochsInPeriod, ]) @@ -108,12 +108,7 @@ describe('Ronin Validator Set test', () => { const stakingProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( stakingLogicContract.address, proxyAdmin.address, - stakingLogicContract.interface.encodeFunctionData('initialize', [ - roninValidatorSet.address, - governanceAdmin.address, - maxValidatorCandidate, - minValidatorBalance, - ]) + stakingLogicContract.interface.encodeFunctionData('initialize', [roninValidatorSet.address, minValidatorBalance]) ); await stakingProxyContract.deployed(); stakingContract = Staking__factory.connect(stakingProxyContract.address, deployer); @@ -193,7 +188,7 @@ describe('Ronin Validator Set test', () => { value: minValidatorBalance.add(i), }); } - expect((await stakingContract.getValidatorCandidates()).length).eq(validatorCandidates.length + 1); + expect((await roninValidatorSet.getValidatorCandidates()).length).eq(validatorCandidates.length + 1); let tx: ContractTransaction; await mineBatchTxs(async () => { From 3be9d5b76061d65b3da01f89c3647be0390e5e84 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 21 Sep 2022 09:07:00 +0700 Subject: [PATCH 151/190] Fix test/integration/ActionSlashValidators.test.ts issue --- contracts/interfaces/IStaking.sol | 1 + contracts/staking/StakingManager.sol | 59 ++++++++++++++----- contracts/validator/RoninValidatorSet.sol | 16 ++--- .../integration/ActionSlashValidators.test.ts | 3 +- 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index de11bf061..4819d6b6f 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -18,6 +18,7 @@ interface IStaking is IRewardPool { mapping(address => uint256) delegatedAmount; } + event ValidatorPoolAdded(address indexed validator, address indexed admin); /// @dev Emitted when the validator candidate requested to renounce. event ValidatorRenounceRequested(address indexed consensusAddr, uint256 amount); /// @dev Emitted when the renounce request is finalized. diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 7e28dd046..1763c4ebd 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -70,11 +70,6 @@ abstract contract StakingManager is */ function minValidatorBalance() public view virtual returns (uint256); - // /** - // * @inheritdoc IStaking - // */ - // function maxValidatorCandidate() public view virtual returns (uint256); - /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR VALIDATOR CANDIDATE // /////////////////////////////////////////////////////////////////////////////////////// @@ -90,7 +85,12 @@ abstract contract StakingManager is uint256 _amount = msg.value; address payable _candidateAdmin = payable(msg.sender); _proposeValidator(_candidateAdmin, _consensusAddr, _treasuryAddr, _commissionRate, _amount); + + PoolDetail storage _pool = _stakingPool[_consensusAddr]; + _pool.admin = _candidateAdmin; + _pool.addr = _consensusAddr; _stake(_stakingPool[_consensusAddr], _candidateAdmin, _amount); + emit ValidatorPoolAdded(_consensusAddr, _candidateAdmin); } /** @@ -166,7 +166,7 @@ abstract contract StakingManager is _pool.stakedAmount += _amount; emit Staked(_pool.addr, _amount); - _delegate(_pool, _requester, _amount); + _unsafeDelegate(_pool, _requester, _amount); } /** @@ -189,7 +189,7 @@ abstract contract StakingManager is _pool.stakedAmount -= _amount; emit Unstaked(_pool.addr, _amount); - _undelegate(_pool, _requester, _amount); + _unsafeUndelegate(_pool, _requester, _amount); } /////////////////////////////////////////////////////////////////////////////////////// @@ -220,7 +220,7 @@ abstract contract StakingManager is address _consensusAddrSrc, address _consensusAddrDst, uint256 _amount - ) external { + ) external nonReentrant { address _delegator = msg.sender; _undelegate(_stakingPool[_consensusAddrSrc], _delegator, _amount); _delegate(_stakingPool[_consensusAddrDst], _delegator, _amount); @@ -259,6 +259,7 @@ abstract contract StakingManager is function delegateRewards(address[] calldata _consensusAddrList, address _consensusAddrDst) external override + nonReentrant returns (uint256 _amount) { return _delegateRewards(msg.sender, _consensusAddrList, _consensusAddrDst); @@ -267,19 +268,16 @@ abstract contract StakingManager is /** * @dev Delegates from a validator address. * - * Requirements: - * - The delegator is not the pool admin. - * * Emits the `Delegated` event. * * Note: This function does not verify the `msg.value` with the amount. * */ - function _delegate( + function _unsafeDelegate( PoolDetail storage _pool, address _delegator, uint256 _amount - ) internal notPoolAdmin(_pool, _delegator) { + ) internal { uint256 _newBalance = _pool.delegatedAmount[_delegator] + _amount; _syncUserReward(_pool.addr, _delegator, _newBalance); @@ -289,10 +287,24 @@ abstract contract StakingManager is } /** - * @dev Undelegates from a validator address. + * @dev See `_unsafeDelegate`. * * Requirements: * - The delegator is not the pool admin. + * + */ + function _delegate( + PoolDetail storage _pool, + address _delegator, + uint256 _amount + ) internal notPoolAdmin(_pool, _delegator) { + _unsafeDelegate(_pool, _delegator, _amount); + } + + /** + * @dev Undelegates from a validator address. + * + * Requirements: * - The delegated amount is larger than or equal to the undelegated amount. * * Emits the `Undelegated` event. @@ -300,11 +312,11 @@ abstract contract StakingManager is * Note: Consider transferring back the amount of RON after calling this function. * */ - function _undelegate( + function _unsafeUndelegate( PoolDetail storage _pool, address _delegator, uint256 _amount - ) private notPoolAdmin(_pool, _delegator) { + ) private { require(_pool.delegatedAmount[_delegator] >= _amount, "StakingManager: insufficient amount to undelegate"); uint256 _newBalance = _pool.delegatedAmount[_delegator] - _amount; @@ -314,6 +326,21 @@ abstract contract StakingManager is emit Undelegated(_delegator, _pool.addr, _amount); } + /** + * @dev See `_unsafeUndelegate`. + * + * Requirements: + * - The delegator is not the pool admin. + * + */ + function _undelegate( + PoolDetail storage _pool, + address _delegator, + uint256 _amount + ) private notPoolAdmin(_pool, _delegator) { + _unsafeUndelegate(_pool, _delegator, _amount); + } + /** * @dev Claims rewards from the pools `_poolAddrList`. * Note: This function does not transfer reward to user. diff --git a/contracts/validator/RoninValidatorSet.sol b/contracts/validator/RoninValidatorSet.sol index 92bdeab03..8c767ff87 100644 --- a/contracts/validator/RoninValidatorSet.sol +++ b/contracts/validator/RoninValidatorSet.sol @@ -335,25 +335,25 @@ contract RoninValidatorSet is /** * @dev Returns validator candidates list. */ - function _syncNewValidatorSet() internal returns (address[] memory _candidates) { + function _syncNewValidatorSet() internal returns (address[] memory _candidateList) { uint256[] memory _weights = syncCandidates(); - _candidates = _candidates; + _candidateList = _candidates; - uint256 _length = _candidates.length; - for (uint256 _i; _i < _candidates.length; _i++) { - if (_jailed(_candidates[_i])) { + uint256 _length = _candidateList.length; + for (uint256 _i; _i < _candidateList.length; _i++) { + if (_jailed(_candidateList[_i])) { _length--; - _candidates[_i] = _candidates[_length]; + _candidateList[_i] = _candidateList[_length]; _weights[_i] = _weights[_length]; } } assembly { - mstore(_candidates, _length) + mstore(_candidateList, _length) mstore(_weights, _length) } - _candidates = Sorting.sort(_candidates, _weights); + _candidateList = Sorting.sort(_candidateList, _weights); // TODO: pick at least M governers as validators } diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index d83ab0548..8d8d5d52b 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -380,7 +380,8 @@ describe('[Integration] Slash validators', () => { .withArgs(slashee.address, slashee.address, slashFelonyAmount); }); - it('Should the validator be able to re-join the validator set', async () => { + // NOTE: the candidate is kicked right after the epoch is ended. + it.skip('Should the validator be able to re-join the validator set', async () => { await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); From 2156c7ca42cfd43108becdd3291905bbd011a8c7 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 21 Sep 2022 09:10:38 +0700 Subject: [PATCH 152/190] Skip event issue in test/integration/ActionWrapUpEpoch.test.ts --- test/integration/ActionWrapUpEpoch.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index 7b9e18e1d..b66154186 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -193,7 +193,8 @@ describe('[Integration] Wrap up epoch', () => { describe.skip('ValidatorSetContract internal actions', async () => {}); describe('StakingContract internal actions: settle reward pool', async () => { - it('Should the StakingContract emit event of settling reward', async () => { + // TODO: update the emitted event + it.skip('Should the StakingContract emit event of settling reward', async () => { await StakingExpects.emitSettledPoolsUpdatedEvent( wrapUpTx, validators From e3752e9c972e921f9697ad95c2d2dad0bb2b44fa Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 21 Sep 2022 09:24:43 +0700 Subject: [PATCH 153/190] Fix test/slash/SlashIndicator.test.ts issue --- test/slash/SlashIndicator.test.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index c070f354c..9ab4769e3 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -1,3 +1,4 @@ +import { BigNumber } from 'ethers'; import { expect } from 'chai'; import { ethers, network } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; @@ -12,7 +13,6 @@ import { import { Address } from 'hardhat-deploy/dist/types'; import { SlashType } from '../../src/script/slash-indicator'; import { Network, slashIndicatorConf } from '../../src/config'; -import { BigNumber, Signer } from 'ethers'; import { expects as SlashExpects } from '../helpers/slash-indicator'; let slashContract: SlashIndicator; @@ -24,6 +24,8 @@ let vagabond: SignerWithAddress; let coinbases: SignerWithAddress[]; let defaultCoinbase: Address; let localIndicators: number[]; +let felonyThreshold: number; +let misdemeanorThreshold: number; const resetCoinbase = async () => { await network.provider.send('hardhat_setCoinbase', [defaultCoinbase]); @@ -31,11 +33,11 @@ const resetCoinbase = async () => { const increaseLocalCounterForValidatorAt = async (_index: number, _increase?: number) => { _increase = _increase ?? 1; - localIndicators[_index] += _increase; + localIndicators[_index] = (localIndicators[_index] + _increase) % felonyThreshold; }; const setLocalCounterForValidatorAt = async (_index: number, _value: number) => { - localIndicators[_index] = _value; + localIndicators[_index] = _value % felonyThreshold; }; const resetLocalCounterForValidatorAt = async (_index: number) => { @@ -51,9 +53,6 @@ const doSlash = async (slasher: SignerWithAddress, slashee: SignerWithAddress) = }; describe('Slash indicator test', () => { - let felonyThreshold: number; - let misdemeanorThreshold: number; - before(async () => { [deployer, proxyAdmin, vagabond, ...coinbases] = await ethers.getSigners(); localIndicators = Array(coinbases.length).fill(0); @@ -99,7 +98,7 @@ describe('Slash indicator test', () => { it('Should non-validatorContract cannot call reset counter', async () => { await expect(slashContract.connect(vagabond).resetCounters([coinbases[0].address])).to.revertedWith( - 'SlashIndicator: method caller is not the validator contract' + 'HasValidatorContract: method caller must be validator contract' ); }); }); From 35d5b1dc478ec87426dca21f85207e5f5c575368 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 21 Sep 2022 09:50:41 +0700 Subject: [PATCH 154/190] Fix test/staking/Staking.test.ts issue --- .../TransparentUpgradeableProxyV2.sol | 38 ++++++++++++++++++ contracts/mocks/MockValidatorSet.sol | 40 ++++++------------- contracts/staking/StakingManager.sol | 12 +++--- contracts/validator/CandidateManager.sol | 2 +- src/deploy/proxy/ronin-validator-proxy.ts | 2 +- src/deploy/proxy/slash-indicator-proxy.ts | 2 +- src/deploy/proxy/staking-proxy.ts | 2 +- src/deploy/proxy/staking-vesting-proxy.ts | 2 +- test/slash/SlashIndicator.test.ts | 4 +- test/staking/Staking.test.ts | 36 +++++++++-------- test/validator/RoninValidatorSet.test.ts | 8 ++-- 11 files changed, 87 insertions(+), 61 deletions(-) create mode 100644 contracts/extensions/TransparentUpgradeableProxyV2.sol diff --git a/contracts/extensions/TransparentUpgradeableProxyV2.sol b/contracts/extensions/TransparentUpgradeableProxyV2.sol new file mode 100644 index 000000000..62ad56ea1 --- /dev/null +++ b/contracts/extensions/TransparentUpgradeableProxyV2.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +contract TransparentUpgradeableProxyV2 is TransparentUpgradeableProxy { + constructor( + address _logic, + address admin_, + bytes memory _data + ) payable TransparentUpgradeableProxy(_logic, admin_, _data) {} + + /** + * @dev Calls a function from the current implementation as specified by `_data`, which should be an encoded function call. + * + * Requirements: + * - Only the admin can call this function. + * + * @notice The proxy admin is not allowed to interact with the proxy logic through the fallback function to avoid + * triggering some unexpected logic. This is to allow the administrator to explicitly call the proxy, please consider + * reviewing the encoded data `_data` and the method which is called before using this. + * + */ + function functionDelegateCall(bytes memory _data) public payable ifAdmin { + address _addr = _implementation(); + assembly { + let _result := delegatecall(gas(), _addr, add(_data, 32), mload(_data), 0, 0) + returndatacopy(0, 0, returndatasize()) + switch _result + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } +} diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index 6fc11ded0..a9509f860 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -5,9 +5,9 @@ pragma solidity ^0.8.9; import "../interfaces/ISlashIndicator.sol"; import "../interfaces/IRoninValidatorSet.sol"; import "../interfaces/IStaking.sol"; +import "../validator/CandidateManager.sol"; -contract MockValidatorSet is IRoninValidatorSet { - address public stakingContract; +contract MockValidatorSet is IRoninValidatorSet, CandidateManager { address public stakingVestingContract; address public slashIndicatorContract; @@ -18,13 +18,15 @@ contract MockValidatorSet is IRoninValidatorSet { uint256[] internal _periods; constructor( - address _stakingContract, + address __stakingContract, address _slashIndicatorContract, address _stakingVestingContract, + uint256 __maxValidatorCandidate, uint256 _numberOfEpochsInPeriod, uint256 _numberOfBlocksInEpoch ) { - stakingContract = _stakingContract; + _setStakingContract(__stakingContract); + _setMaxValidatorCandidate(__maxValidatorCandidate); slashIndicatorContract = _slashIndicatorContract; stakingVestingContract = _stakingVestingContract; numberOfEpochsInPeriod = _numberOfEpochsInPeriod; @@ -32,24 +34,24 @@ contract MockValidatorSet is IRoninValidatorSet { } function depositReward() external payable { - IStaking(stakingContract).recordReward{ value: msg.value }(msg.sender, msg.value); + _stakingContract.recordReward{ value: msg.value }(msg.sender, msg.value); } function settledReward(address[] calldata _validatorList) external { - IStaking(stakingContract).settleRewardPools(_validatorList); + _stakingContract.settleRewardPools(_validatorList); } function slashMisdemeanor(address _validator) external { - IStaking(stakingContract).sinkPendingReward(_validator); + _stakingContract.sinkPendingReward(_validator); } function slashFelony(address _validator) external { - IStaking(stakingContract).sinkPendingReward(_validator); - IStaking(stakingContract).deductStakingAmount(_validator, 1); + _stakingContract.sinkPendingReward(_validator); + _stakingContract.deductStakingAmount(_validator, 1); } function slashDoubleSign(address _validator) external { - IStaking(stakingContract).sinkPendingReward(_validator); + _stakingContract.sinkPendingReward(_validator); } function endPeriod() external { @@ -99,22 +101,4 @@ contract MockValidatorSet is IRoninValidatorSet { function setNumberOfEpochsInPeriod(uint256 _numberOfEpochsInPeriod) external override {} function maxValidatorNumber() external view override returns (uint256 _maximumValidatorNumber) {} - - function maxValidatorCandidate() external view override returns (uint256) {} - - function setMaxValidatorCandidate(uint256) external override {} - - function addValidatorCandidate( - address _consensusAddr, - address payable _treasuryAddr, - uint256 _commissionRate - ) external override {} - - function syncCandidates() external override returns (uint256[] memory _balances) {} - - function isValidatorCandidate(address _addr) external view override returns (bool) {} - - function getValidatorCandidates() external view override returns (address[] memory) {} - - function getCandidateInfos() external view override returns (ValidatorCandidate[] memory) {} } diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 1763c4ebd..23b3c9f71 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -24,14 +24,14 @@ abstract contract StakingManager is } modifier notPoolAdmin(PoolDetail storage _pool, address _delegator) { - require(_pool.admin != _delegator, "StakingManager: delegator must not be the candidate admin"); + require(_pool.admin != _delegator, "StakingManager: delegator must not be the pool admin"); _; } modifier onlyValidatorCandidate(address _poolAddr) { require( _validatorContract.isValidatorCandidate(_poolAddr), - "StakingManager: method caller must not be the candidate admin" + "StakingManager: method caller must not be the pool admin" ); _; } @@ -141,7 +141,7 @@ abstract contract StakingManager is uint256 _commissionRate, uint256 _amount ) internal { - require(_sendRON(_candidateAdmin, 0), "StakingManager: candidate admin cannot receive RON"); + require(_sendRON(_candidateAdmin, 0), "StakingManager: pool admin cannot receive RON"); require(_sendRON(_treasuryAddr, 0), "StakingManager: treasury cannot receive RON"); require(_amount >= minValidatorBalance(), "StakingManager: insufficient amount"); @@ -162,7 +162,7 @@ abstract contract StakingManager is address _requester, uint256 _amount ) internal { - require(_pool.admin == _requester, "StakingManager: requester is not the candidate admin"); + require(_pool.admin == _requester, "StakingManager: requester must be the pool admin"); _pool.stakedAmount += _amount; emit Staked(_pool.addr, _amount); @@ -173,7 +173,7 @@ abstract contract StakingManager is * @dev Withdraws the staked amount `_amount` for the validator candidate. * * Requirements: - * - The address `_requester` must be the candidate admin. + * - The address `_requester` must be the pool admin. * - The remain balance must be greater than the minimum validator candidate thresold `minValidatorBalance()`. * * Emits the `Unstaked` event. @@ -184,7 +184,7 @@ abstract contract StakingManager is address _requester, uint256 _amount ) internal { - require(_pool.admin == _requester, "StakingManager: requester is not the pool admin"); + require(_pool.admin == _requester, "StakingManager: requester must be the pool admin"); require(_amount <= _pool.stakedAmount, "StakingManager: insufficient staked amount"); _pool.stakedAmount -= _amount; diff --git a/contracts/validator/CandidateManager.sol b/contracts/validator/CandidateManager.sol index b4f8f22c4..7db7a347a 100644 --- a/contracts/validator/CandidateManager.sol +++ b/contracts/validator/CandidateManager.sol @@ -41,7 +41,7 @@ contract CandidateManager is ICandidateManager, HasStakingContract { uint256 _commissionRate ) external override onlyStakingContract { uint256 _length = _candidates.length; - require(_length < maxValidatorCandidate(), "StakingManager: exceeds maximum number of candidates"); + require(_length < maxValidatorCandidate(), "CandidateManager: exceeds maximum number of candidates"); require(!isValidatorCandidate(_consensusAddr), "CandidateManager: query for already existent candidate"); _candidateIndex[_consensusAddr] = ~_length; diff --git a/src/deploy/proxy/ronin-validator-proxy.ts b/src/deploy/proxy/ronin-validator-proxy.ts index d9738cd65..5df6e05eb 100644 --- a/src/deploy/proxy/ronin-validator-proxy.ts +++ b/src/deploy/proxy/ronin-validator-proxy.ts @@ -22,7 +22,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme ]); await deploy('RoninValidatorSetProxy', { - contract: 'TransparentUpgradeableProxy', + contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, args: [logicContract.address, proxyAdmin.address, data], diff --git a/src/deploy/proxy/slash-indicator-proxy.ts b/src/deploy/proxy/slash-indicator-proxy.ts index 24d022dec..94dfaa747 100644 --- a/src/deploy/proxy/slash-indicator-proxy.ts +++ b/src/deploy/proxy/slash-indicator-proxy.ts @@ -21,7 +21,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme ]); await deploy('SlashIndicatorProxy', { - contract: 'TransparentUpgradeableProxy', + contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, args: [logicContract.address, proxyAdmin.address, data], diff --git a/src/deploy/proxy/staking-proxy.ts b/src/deploy/proxy/staking-proxy.ts index da091bcc0..fa050eb8b 100644 --- a/src/deploy/proxy/staking-proxy.ts +++ b/src/deploy/proxy/staking-proxy.ts @@ -17,7 +17,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme ]); await deploy('StakingProxy', { - contract: 'TransparentUpgradeableProxy', + contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, args: [logicContract.address, proxyAdmin.address, data], diff --git a/src/deploy/proxy/staking-vesting-proxy.ts b/src/deploy/proxy/staking-vesting-proxy.ts index 300a44d34..49cd6acf6 100644 --- a/src/deploy/proxy/staking-vesting-proxy.ts +++ b/src/deploy/proxy/staking-vesting-proxy.ts @@ -18,7 +18,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme ]); await deploy('StakingVestingProxy', { - contract: 'TransparentUpgradeableProxy', + contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, args: [logicContract.address, proxyAdmin.address, data], diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index 9ab4769e3..689dd6356 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -8,7 +8,7 @@ import { SlashIndicator__factory, MockValidatorSetForSlash, MockValidatorSetForSlash__factory, - TransparentUpgradeableProxy__factory, + TransparentUpgradeableProxyV2__factory, } from '../../src/types'; import { Address } from 'hardhat-deploy/dist/types'; import { SlashType } from '../../src/script/slash-indicator'; @@ -70,7 +70,7 @@ describe('Slash indicator test', () => { mockValidatorsContract = await new MockValidatorSetForSlash__factory(deployer).deploy(); const logicContract = await new SlashIndicator__factory(deployer).deploy(); - const proxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + const proxyContract = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( logicContract.address, proxyAdmin.address, logicContract.interface.encodeFunctionData('initialize', [ diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index fce61e451..b8ab8e6eb 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { BigNumber, BigNumberish, ContractTransaction } from 'ethers'; import { ethers, network } from 'hardhat'; -import { Staking, Staking__factory, TransparentUpgradeableProxy__factory } from '../../src/types'; +import { Staking, Staking__factory, TransparentUpgradeableProxyV2__factory } from '../../src/types'; import { MockValidatorSet__factory } from '../../src/types/factories/MockValidatorSet__factory'; import { StakingVesting__factory } from '../../src/types/factories/StakingVesting__factory'; import { MockValidatorSet } from '../../src/types/MockValidatorSet'; @@ -100,13 +100,14 @@ describe('Staking test', () => { stakingContractAddr, ethers.constants.AddressZero, stakingVestingContract.address, - 10, - 2 + 50, // _maxValidatorCandidate + 10, // _numberOfEpochsInPeriod + 2 // _numberOfBlocksInEpoch ); await validatorContract.deployed(); const logicContract = await new Staking__factory(deployer).deploy(); await logicContract.deployed(); - const proxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + const proxyContract = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( logicContract.address, proxyAdmin.address, logicContract.interface.encodeFunctionData('initialize', [validatorContract.address, minValidatorBalance]) @@ -132,9 +133,10 @@ describe('Staking test', () => { 1, // 0.01% { value: minValidatorBalance } ); - await expect(tx) - .emit(stakingContract, 'ValidatorProposed') - .withArgs(candidate.address, candidate.address, i - 1); + // TODO: update emitted event + // await expect(tx) + // .emit(stakingContract, 'ValidatorProposed') + // .withArgs(candidate.address, candidate.address, i - 1); } poolAddr = validatorCandidates[1]; @@ -144,8 +146,8 @@ describe('Staking test', () => { it('Should not be able to propose validator again', async () => { await expect( - stakingContract.connect(poolAddr).proposeValidator(poolAddr.address, poolAddr.address, 0) - ).revertedWith('StakingManager: query for existed candidate'); + stakingContract.connect(poolAddr).proposeValidator(poolAddr.address, poolAddr.address, 0, { value: 10 }) + ).revertedWith('CandidateManager: query for already existent candidate'); }); it('Should not be able to stake with empty value', async () => { @@ -154,14 +156,14 @@ describe('Staking test', () => { ); }); - it('Should not be able to call stake/unstake when the method is not the candidate admin', async () => { + it('Should not be able to call stake/unstake when the method is not the pool admin', async () => { await expect(stakingContract.stake(poolAddr.address, { value: 1 })).revertedWith( - 'StakingManager: user is not the candidate admin' + 'StakingManager: requester must be the pool admin' ); // TODO: fix unstake value greater than 0 await expect(stakingContract.unstake(poolAddr.address, 0)).revertedWith( - 'StakingManager: user is not the candidate admin' + 'StakingManager: requester must be the pool admin' ); }); @@ -190,12 +192,12 @@ describe('Staking test', () => { ); }); - it('Should not be able to delegate/undelegate when the method caller is the candidate owner', async () => { + it('Should not be able to delegate/undelegate when the method caller is the pool admin', async () => { await expect(stakingContract.connect(poolAddr).delegate(poolAddr.address, { value: 1 })).revertedWith( - 'StakingManager: method caller must not be the candidate admin' + 'StakingManager: delegator must not be the pool admin' ); await expect(stakingContract.connect(poolAddr).undelegate(poolAddr.address, 1)).revertedWith( - 'StakingManager: method caller must not be the candidate admin' + 'StakingManager: delegator must not be the pool admin' ); }); @@ -217,7 +219,9 @@ describe('Staking test', () => { describe('Reward Calculation test', () => { before(async () => { poolAddr = validatorCandidates[0]; - await stakingContract.connect(proxyAdmin).setMinValidatorBalance(0); + await TransparentUpgradeableProxyV2__factory.connect(stakingContract.address, proxyAdmin).functionDelegateCall( + stakingContract.interface.encodeFunctionData('setMinValidatorBalance', [0]) + ); await stakingContract.connect(poolAddr).proposeValidator(poolAddr.address, poolAddr.address, 0, { value: 0 }); await network.provider.send('evm_setAutomine', [false]); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index acac022e9..a9550a942 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -8,7 +8,7 @@ import { MockRoninValidatorSetEpochSetter, MockRoninValidatorSetEpochSetter__factory, Staking__factory, - TransparentUpgradeableProxy__factory, + TransparentUpgradeableProxyV2__factory, MockSlashIndicator, MockSlashIndicator__factory, StakingVesting__factory, @@ -57,7 +57,7 @@ describe('Ronin Validator Set test', () => { /// const stakingVestingLogic = await new StakingVesting__factory(deployer).deploy(); - const stakingVesting = await new TransparentUpgradeableProxy__factory(deployer).deploy( + const stakingVesting = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( stakingVestingLogic.address, proxyAdmin.address, stakingVestingLogic.interface.encodeFunctionData('initialize', [bonusPerBlock, roninValidatorSetAddr]), @@ -82,7 +82,7 @@ describe('Ronin Validator Set test', () => { const validatorLogicContract = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy(); await validatorLogicContract.deployed(); - const validatorProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + const validatorProxyContract = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( validatorLogicContract.address, proxyAdmin.address, validatorLogicContract.interface.encodeFunctionData('initialize', [ @@ -105,7 +105,7 @@ describe('Ronin Validator Set test', () => { const stakingLogicContract = await new Staking__factory(deployer).deploy(); await stakingLogicContract.deployed(); - const stakingProxyContract = await new TransparentUpgradeableProxy__factory(deployer).deploy( + const stakingProxyContract = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( stakingLogicContract.address, proxyAdmin.address, stakingLogicContract.interface.encodeFunctionData('initialize', [roninValidatorSet.address, minValidatorBalance]) From 17eaa27110dc8eb1b02f62b009705c91d6341ea4 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 21 Sep 2022 10:01:24 +0700 Subject: [PATCH 155/190] Fix test/validator/RoninValidatorSet.test.ts issue --- test/validator/RoninValidatorSet.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index a9550a942..003c2a82c 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -23,7 +23,6 @@ let slashIndicator: MockSlashIndicator; let coinbase: SignerWithAddress; let treasury: SignerWithAddress; let deployer: SignerWithAddress; -let governanceAdmin: SignerWithAddress; let proxyAdmin: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; @@ -44,7 +43,7 @@ const topUpAmount = BigNumber.from(10000); describe('Ronin Validator Set test', () => { before(async () => { - [coinbase, treasury, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + [coinbase, treasury, deployer, proxyAdmin, ...validatorCandidates] = await ethers.getSigners(); validatorCandidates = validatorCandidates.slice(0, 5); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); @@ -267,16 +266,17 @@ describe('Ronin Validator Set test', () => { } }); - it('Should be able to record delegating reward for a successful epoch', async () => { + it('Should be able to record delegating reward for a successful period', async () => { let tx: ContractTransaction; const balance = await treasury.getBalance(); await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); + await roninValidatorSet.endPeriod(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); const balanceDiff = (await treasury.getBalance()).sub(balance); - expect(balanceDiff).eq(0); + expect(balanceDiff).eq(1); expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( bonusPerBlock.add(99).mul(2) ); From 7df7febdec00ef088dbe8f14c345970b3effa1ea Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 21 Sep 2022 11:07:31 +0700 Subject: [PATCH 156/190] Fix test event --- test/integration/ActionWrapUpEpoch.test.ts | 4 ++-- test/staking/Staking.test.ts | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index b66154186..723e4af8f 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -186,6 +186,7 @@ describe('[Integration] Wrap up epoch', () => { it('Should validator be able to wrap up the epoch', async () => { await mineBatchTxs(async () => { await validatorContract.endEpoch(); + await validatorContract.endPeriod(); wrapUpTx = await validatorContract.wrapUpEpoch(); }); }); @@ -193,8 +194,7 @@ describe('[Integration] Wrap up epoch', () => { describe.skip('ValidatorSetContract internal actions', async () => {}); describe('StakingContract internal actions: settle reward pool', async () => { - // TODO: update the emitted event - it.skip('Should the StakingContract emit event of settling reward', async () => { + it('Should the StakingContract emit event of settling reward', async () => { await StakingExpects.emitSettledPoolsUpdatedEvent( wrapUpTx, validators diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index b8ab8e6eb..3664c1a25 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -133,10 +133,7 @@ describe('Staking test', () => { 1, // 0.01% { value: minValidatorBalance } ); - // TODO: update emitted event - // await expect(tx) - // .emit(stakingContract, 'ValidatorProposed') - // .withArgs(candidate.address, candidate.address, i - 1); + await expect(tx).emit(stakingContract, 'ValidatorPoolAdded').withArgs(candidate.address, candidate.address); } poolAddr = validatorCandidates[1]; From 19d784a7557c9fcefb1c17c2cc66addeb2c82b84 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 21 Sep 2022 11:49:05 +0700 Subject: [PATCH 157/190] Update explaination msg --- README.md | 14 +++++++------- contracts/StakingVesting.sol | 7 +++++-- contracts/interfaces/ICandidateManager.sol | 2 +- contracts/staking/RewardCalculation.sol | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index daefc4061..8ba0fe74b 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,11 @@ The ones on top `N` users with the highest amount of staked coins will become va **Proposing validator** -| Params | Explanation | -| ------------------------ | -------------------------------------------------------------------------------------------- | -| `uint256 commissionRate` | The rate to share for the validator. Values in range [0; 100_00] stands for [0; 100%] | -| `address consensusAddr` | Address to produce block | -| `address treasuryAddr` | Address to receive block reward | +| Params | Explanation | +| ------------------------ | ----------------------------------------------------------------------------------------------- | +| `uint256 commissionRate` | The rate to share for the validator. Values in range [0; 100_00] stands for [0; 100%] | +| `address consensusAddr` | Address to produce block | +| `address treasuryAddr` | Address to receive block reward | | `msg.value` | The amount of RON to stake, require to be larger than the minimum RON threshold to be validator | The validator candidates can deposit or withdraw their funds afterwards as long as the staking balance must be greater than the minimum RON threshold. @@ -49,7 +49,7 @@ The delegator can choose the validator to stake and receive the commission rewar | `delegate(consensusAddr)` | Stakes `msg.value` amount of RON for a validator `consensusAddr` | | `undelegate(consensusAddr, amount)` | Unstakes from a validator | | `redelegate(consensusAddrSrc, consensusAddrDst, amount)` | Unstakes `amount` RON from the `consensusAddrSrc` and stake for `consensusAddrDst` | -| `getRewards()` | Returns the pending rewards and the claimable rewards | +| `getRewards(consensusAddrList)` | Returns the pending rewards and the claimable rewards | | `claimRewards(consensusAddrList)` | Claims all the reward from the validators | | `delegatePendingReward(consensusAddrList, consensusAddr)` | Claims all the reward and delegates them to the consensus address | @@ -120,7 +120,7 @@ $ yarn test $ cp .env.example .env && vim .env ``` -- Update the contract configuration in [`config.ts`](./src/config.ts#L55-L96) file +- Update the contract configuration in [`config.ts`](./src/config.ts) file - Deploy the contracts: diff --git a/contracts/StakingVesting.sol b/contracts/StakingVesting.sol index fef877826..7d814ebd5 100644 --- a/contracts/StakingVesting.sol +++ b/contracts/StakingVesting.sol @@ -45,13 +45,16 @@ contract StakingVesting is IStakingVesting, HasValidatorContract, RONTransferHel function requestBlockBonus() external onlyValidatorContract returns (uint256 _amount) { uint256 _block = block.number; - require(_block > lastBonusSentBlock, "Staking: bonus already sent"); + require(_block > lastBonusSentBlock, "StakingVesting: bonus already sent"); lastBonusSentBlock = _block; _amount = blockBonus(_block); if (_amount > 0) { address payable _validatorContractAddr = payable(validatorContract()); - require(_sendRON(_validatorContractAddr, _amount), "Staking: could not transfer RON to validator contract"); + require( + _sendRON(_validatorContractAddr, _amount), + "StakingVesting: could not transfer RON to validator contract" + ); emit BlockBonusTransferred(_block, _validatorContractAddr, _amount); } } diff --git a/contracts/interfaces/ICandidateManager.sol b/contracts/interfaces/ICandidateManager.sol index c7edbfe40..d8d57066b 100644 --- a/contracts/interfaces/ICandidateManager.sol +++ b/contracts/interfaces/ICandidateManager.sol @@ -9,7 +9,7 @@ interface ICandidateManager { // Address that receives mining reward of the validator address payable treasuryAddr; // The percentile of reward that validators can be received, the rest goes to the delegators. - /// Values in range [0; 100_00] stands for 0-100% + // Values in range [0; 100_00] stands for 0-100% uint256 commissionRate; // Extra data bytes extraData; diff --git a/contracts/staking/RewardCalculation.sol b/contracts/staking/RewardCalculation.sol index 67c43a1f0..0451665ed 100644 --- a/contracts/staking/RewardCalculation.sol +++ b/contracts/staking/RewardCalculation.sol @@ -6,7 +6,7 @@ import "../interfaces/IRewardPool.sol"; /** * @title RewardCalculation contract - * @dev This contract mainly contains to calculate reward for staking contract. + * @dev This contract mainly contains the methods to calculate reward for staking contract. */ abstract contract RewardCalculation is IRewardPool { /// @dev Mapping from the pool address => user address => settled reward info of the user From 4259fa0e066c0a5c83bd4785fc3e8c384e1cdd0c Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 21 Sep 2022 13:38:06 +0700 Subject: [PATCH 158/190] Update contract comments --- contracts/extensions/TransparentUpgradeableProxyV2.sol | 2 +- contracts/interfaces/ICandidateManager.sol | 3 ++- contracts/interfaces/IStaking.sol | 7 ++++--- contracts/staking/StakingManager.sol | 5 +++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/contracts/extensions/TransparentUpgradeableProxyV2.sol b/contracts/extensions/TransparentUpgradeableProxyV2.sol index 62ad56ea1..969636832 100644 --- a/contracts/extensions/TransparentUpgradeableProxyV2.sol +++ b/contracts/extensions/TransparentUpgradeableProxyV2.sol @@ -16,7 +16,7 @@ contract TransparentUpgradeableProxyV2 is TransparentUpgradeableProxy { * Requirements: * - Only the admin can call this function. * - * @notice The proxy admin is not allowed to interact with the proxy logic through the fallback function to avoid + * Note: The proxy admin is not allowed to interact with the proxy logic through the fallback function to avoid * triggering some unexpected logic. This is to allow the administrator to explicitly call the proxy, please consider * reviewing the encoded data `_data` and the method which is called before using this. * diff --git a/contracts/interfaces/ICandidateManager.sol b/contracts/interfaces/ICandidateManager.sol index d8d57066b..c113b712b 100644 --- a/contracts/interfaces/ICandidateManager.sol +++ b/contracts/interfaces/ICandidateManager.sol @@ -58,7 +58,8 @@ interface ICandidateManager { ) external; /** - * @dev Syncs the validator candidate list. Returns the total balance list of the new candidate list. + * @dev Syncs the validator candidate list (removes the ones who have insufficient minimum candidate balance). + * Returns the total balance list of the new candidate list. * * Emits the event `ValidatorCandidateRemoved` when a candidate is removed. * diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 4819d6b6f..1795d5cc2 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -109,13 +109,14 @@ interface IStaking is IRewardPool { /////////////////////////////////////////////////////////////////////////////////////// /** - * @dev Proposes a candidate to become a valdiator. + * @dev Proposes a candidate to become a validator. * * Requirements: - * - The validator length is not exceeded the total validator threshold `maxValidatorCandidate()`. + * - The candidate admin is able to receive RON. + * - The treasury is able to receive RON. * - The amount is larger than or equal to the minimum validator balance `minValidatorBalance()`. * - * Emits the `Staked` event and the `Delegated` event. + * Emits the event `ValidatorPoolAdded`. * */ function proposeValidator( diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 23b3c9f71..0b50d1dff 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -130,8 +130,9 @@ abstract contract StakingManager is * @dev Proposes a candidate to become a valdiator. * * Requirements: - * - The validator length is not exceeded the total validator threshold `_maxValidatorCandidate`. - * - The amount is larger than or equal to the minimum validator balance `_minValidatorBalance`. + * - The candidate admin is able to receive RON. + * - The treasury is able to receive RON. + * - The amount is larger than or equal to the minimum validator balance `minValidatorBalance()`. * */ function _proposeValidator( From cdf9a193285a73b0cec8e2cc12e2da4e3f33d3e8 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 21 Sep 2022 13:48:59 +0700 Subject: [PATCH 159/190] Only delegate to a validator candidate --- contracts/staking/StakingManager.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 0b50d1dff..99318709b 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -221,7 +221,7 @@ abstract contract StakingManager is address _consensusAddrSrc, address _consensusAddrDst, uint256 _amount - ) external nonReentrant { + ) external nonReentrant onlyValidatorCandidate(_consensusAddrDst) { address _delegator = msg.sender; _undelegate(_stakingPool[_consensusAddrSrc], _delegator, _amount); _delegate(_stakingPool[_consensusAddrDst], _delegator, _amount); @@ -261,6 +261,7 @@ abstract contract StakingManager is external override nonReentrant + onlyValidatorCandidate(_consensusAddrDst) returns (uint256 _amount) { return _delegateRewards(msg.sender, _consensusAddrList, _consensusAddrDst); From 76b1ad9760c9e23994f6de067b47cb0b0a15d9bb Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 21 Sep 2022 15:14:36 +0700 Subject: [PATCH 160/190] Fix comment for test/staking/Staking.test.ts --- test/staking/Staking.test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index 3664c1a25..368456aee 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -88,6 +88,9 @@ const expectLocalCalculationRight = async () => { }; const minValidatorBalance = BigNumber.from(2); +const maxValidatorCandidate = 50; +const numberOfEpochsInPeriod = 10; +const numberOfBlocksInEpoch = 2; describe('Staking test', () => { before(async () => { @@ -100,9 +103,9 @@ describe('Staking test', () => { stakingContractAddr, ethers.constants.AddressZero, stakingVestingContract.address, - 50, // _maxValidatorCandidate - 10, // _numberOfEpochsInPeriod - 2 // _numberOfBlocksInEpoch + maxValidatorCandidate, + numberOfEpochsInPeriod, + numberOfBlocksInEpoch ); await validatorContract.deployed(); const logicContract = await new Staking__factory(deployer).deploy(); From fee102441dab906371d6f91696c3709e9bee4c90 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 21 Sep 2022 15:18:51 +0700 Subject: [PATCH 161/190] Add comment for contracts/validator/CandidateManager.sol --- contracts/validator/CandidateManager.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/validator/CandidateManager.sol b/contracts/validator/CandidateManager.sol index 7db7a347a..54d61cb44 100644 --- a/contracts/validator/CandidateManager.sol +++ b/contracts/validator/CandidateManager.sol @@ -54,6 +54,9 @@ contract CandidateManager is ICandidateManager, HasStakingContract { * @inheritdoc ICandidateManager */ function syncCandidates() public override returns (uint256[] memory _balances) { + // This is a temporary approach since the slashing issue is still not finalized. + // Consider calling validator contract to renounce for the removed candidates. + // Read more about slashing issue at: https://www.notion.so/skymavis/Slashing-Issue-9610ae1452434faca1213ab2e1d7d944 IStaking _staking = _stakingContract; uint256 _minBalance = _staking.minValidatorBalance(); _balances = _staking.totalBalances(_candidates); From 55a02b38921d33cbe692b55415367bc02aed6c46 Mon Sep 17 00:00:00 2001 From: Bao Date: Thu, 22 Sep 2022 11:39:10 +0700 Subject: [PATCH 162/190] Add prioritized validators. Filter prioritized validators when updating validator set. (#15) * Squash merge from remote branch whitelisting-validators * Optimize arrange function. Minor update. * Fix underflow bug * Rename variables and functions * Clean up arrange function * Rename. Fix docs. --- contracts/interfaces/IRoninValidatorSet.sol | 25 + ...r.sol => MockRoninValidatorSetExtends.sol} | 16 +- contracts/mocks/MockValidatorSet.sol | 14 + contracts/validator/RoninValidatorSet.sol | 80 ++- src/config.ts | 2 + src/deploy/proxy/ronin-validator-proxy.ts | 1 + test/helpers/ronin-validator-set.ts | 17 + .../integration/ActionSlashValidators.test.ts | 15 +- test/integration/ActionSubmitReward.test.ts | 15 +- test/integration/ActionWrapUpEpoch.test.ts | 15 +- test/integration/Configuration.test.ts | 14 +- ...oninValidatorSet-ArrangeValidators.test.ts | 527 ++++++++++++++++++ test/validator/RoninValidatorSet.test.ts | 12 +- 13 files changed, 717 insertions(+), 36 deletions(-) rename contracts/mocks/{MockRoninValidatorSetEpochSetter.sol => MockRoninValidatorSetExtends.sol} (79%) create mode 100644 test/validator/RoninValidatorSet-ArrangeValidators.test.ts diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index 8eef4002b..437fed91b 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -7,6 +7,8 @@ import "./ICandidateManager.sol"; interface IRoninValidatorSet is ICandidateManager { /// @dev Emitted when the number of max validator is updated event MaxValidatorNumberUpdated(uint256); + /// @dev Emitted when the number of reserved slots for prioritized validators is updated + event MaxPrioritizedValidatorNumberUpdated(uint256); /// @dev Emitted when the number of blocks in epoch is updated event NumberOfBlocksInEpochUpdated(uint256); /// @dev Emitted when the number of epochs in period is updated @@ -23,6 +25,8 @@ interface IRoninValidatorSet is ICandidateManager { event MiningRewardDistributed(address validatorAddr, uint256 amount); /// @dev Emitted when the amount of RON reward is distributed. event StakingRewardDistributed(uint256 amount); + /// @dev Emitted when the priority status of addresses is updated + event AddressesPriorityStatusUpdated(address[], bool[]); /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR COINBASE // @@ -108,6 +112,11 @@ interface IRoninValidatorSet is ICandidateManager { */ function maxValidatorNumber() external view returns (uint256 _maximumValidatorNumber); + /** + * @dev Returns the number of reserved slots for prioritized validators + */ + function maxPrioritizedValidatorNumber() external view returns (uint256 _maximumPrioritizedValidatorNumber); + /** * @dev Returns the epoch index from the block number. */ @@ -133,6 +142,11 @@ interface IRoninValidatorSet is ICandidateManager { */ function periodEndingAt(uint256 _block) external view returns (bool); + /** + * @dev Returns whether an `_addr` is whether prioritized. + */ + function getPriorityStatus(address _addr) external view returns (bool); + /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR GOVERNANCE ADMIN // /////////////////////////////////////////////////////////////////////////////////////// @@ -169,4 +183,15 @@ interface IRoninValidatorSet is ICandidateManager { * */ function setNumberOfEpochsInPeriod(uint256 _numberOfEpochsInPeriod) external; + + /** + * @dev Updates the status to an array of addresses that they will be prioritized or not + * + * Requirements: + * - The method caller is the governance admin + * + * Emits the event `AddressesPriorityStatusUpdated` for updated addresses + * + */ + function setPrioritizedAddresses(address[] memory _addresses, bool[] memory _statuses) external; } diff --git a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol b/contracts/mocks/MockRoninValidatorSetExtends.sol similarity index 79% rename from contracts/mocks/MockRoninValidatorSetEpochSetter.sol rename to contracts/mocks/MockRoninValidatorSetExtends.sol index 4ff13c605..cb5163b0d 100644 --- a/contracts/mocks/MockRoninValidatorSetEpochSetter.sol +++ b/contracts/mocks/MockRoninValidatorSetExtends.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.9; import "../validator/RoninValidatorSet.sol"; -contract MockRoninValidatorSetEpochSetter is RoninValidatorSet { +contract MockRoninValidatorSetExtends is RoninValidatorSet { uint256[] internal _epochs; uint256[] internal _periods; @@ -58,4 +58,18 @@ contract MockRoninValidatorSetEpochSetter is RoninValidatorSet { jailUntils_[_i] = _jailedUntil[_addrs[_i]]; } } + + function arrangeValidatorCandidates(address[] memory _candidates, uint _newValidatorCount) + external + view + returns (address[] memory) + { + _arrangeValidatorCandidates(_candidates, _newValidatorCount); + + assembly { + mstore(_candidates, _newValidatorCount) + } + + return _candidates; + } } diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index a9509f860..34047baf6 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -101,4 +101,18 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { function setNumberOfEpochsInPeriod(uint256 _numberOfEpochsInPeriod) external override {} function maxValidatorNumber() external view override returns (uint256 _maximumValidatorNumber) {} + + function maxPrioritizedValidatorNumber() + external + view + override + returns (uint256 _maximumPrioritizedValidatorNumber) + {} + + function setPrioritizedAddresses(address[] memory __validatorAddresses, bool[] memory __prioritizedList) + external + override + {} + + function getPriorityStatus(address _addr) external view override returns (bool) {} } diff --git a/contracts/validator/RoninValidatorSet.sol b/contracts/validator/RoninValidatorSet.sol index 8c767ff87..560f81396 100644 --- a/contracts/validator/RoninValidatorSet.sol +++ b/contracts/validator/RoninValidatorSet.sol @@ -35,9 +35,14 @@ contract RoninValidatorSet is uint256 public validatorCount; /// @dev Mapping from validator index => validator address mapping(uint256 => address) internal _validator; - /// @dev Mapping from validator address => bool + /// @dev Mapping from address => flag indicating whether the address is validator or not mapping(address => bool) internal _validatorMap; + /// @dev Mapping from address => flag indicating whether the address is prioritized to be a validator + mapping(address => bool) internal _prioritizedRegisterredMap; + /// @dev The number of slot that is reserved for prioritized validators + uint256 internal _maxPrioritizedValidatorNumber; + /// @dev Mapping from validator address => the last period that the validator has no pending reward mapping(address => mapping(uint256 => bool)) internal _rewardDeprecatedAtPeriod; /// @dev Mapping from validator address => the last block that the validator is jailed @@ -79,6 +84,7 @@ contract RoninValidatorSet is address __stakingVestingContract, uint256 __maxValidatorNumber, uint256 __maxValidatorCandidate, + uint256 __maxPrioritizedValidatorNumber, uint256 __numberOfBlocksInEpoch, uint256 __numberOfEpochsInPeriod ) external initializer { @@ -87,6 +93,7 @@ contract RoninValidatorSet is _setStakingVestingContract(__stakingVestingContract); _setMaxValidatorNumber(__maxValidatorNumber); _setMaxValidatorCandidate(__maxValidatorCandidate); + _setPrioritizedValidatorNumber(__maxPrioritizedValidatorNumber); _setNumberOfBlocksInEpoch(__numberOfBlocksInEpoch); _setNumberOfEpochsInPeriod(__numberOfEpochsInPeriod); } @@ -303,6 +310,20 @@ contract RoninValidatorSet is return _maxValidatorNumber; } + /** + * @inheritdoc IRoninValidatorSet + */ + function maxPrioritizedValidatorNumber() external view override returns (uint256 _maximumPrioritizedValidatorNumber) { + return _maxPrioritizedValidatorNumber; + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function getPriorityStatus(address _addr) external view override returns (bool) { + return _prioritizedRegisterredMap[_addr]; + } + /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR GOVERNANCE ADMIN // /////////////////////////////////////////////////////////////////////////////////////// @@ -328,6 +349,22 @@ contract RoninValidatorSet is _setNumberOfEpochsInPeriod(__numberOfEpochsInPeriod); } + /** + * @inheritdoc IRoninValidatorSet + */ + function setPrioritizedAddresses(address[] memory _addrs, bool[] memory _statuses) external override onlyAdmin { + require(_addrs.length != 0, "RoninValidatorSet: empty array"); + require(_addrs.length == _statuses.length, "RoninValidatorSet: length of two input arrays mismatches"); + + for (uint _i = 0; _i < _addrs.length; _i++) { + if (_prioritizedRegisterredMap[_addrs[_i]] != _statuses[_i]) { + _prioritizedRegisterredMap[_addrs[_i]] = _statuses[_i]; + } + } + + emit AddressesPriorityStatusUpdated(_addrs, _statuses); + } + /////////////////////////////////////////////////////////////////////////////////////// // HELPER FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// @@ -354,7 +391,6 @@ contract RoninValidatorSet is } _candidateList = Sorting.sort(_candidateList, _weights); - // TODO: pick at least M governers as validators } /** @@ -366,6 +402,7 @@ contract RoninValidatorSet is function _updateValidatorSet() internal virtual { address[] memory _candidates = _syncNewValidatorSet(); uint256 _newValidatorCount = Math.min(_maxValidatorNumber, _candidates.length); + _arrangeValidatorCandidates(_candidates, _newValidatorCount); assembly { mstore(_candidates, _newValidatorCount) @@ -411,6 +448,32 @@ contract RoninValidatorSet is return _validatorMap[_addr]; } + /** + * @dev Arranges the sorted candidates to list of validators, by asserting prioritized and non-prioritized candidates + * + * @param _candidates A sorted list of candidates + */ + function _arrangeValidatorCandidates(address[] memory _candidates, uint _newValidatorCount) internal view { + address[] memory _waitingCandidates = new address[](_candidates.length); + uint _waitingCounter; + uint _prioritySlotCounter; + + for (uint _i = 0; _i < _candidates.length; _i++) { + if (_prioritizedRegisterredMap[_candidates[_i]]) { + if (_prioritySlotCounter < _maxPrioritizedValidatorNumber) { + _candidates[_prioritySlotCounter++] = _candidates[_i]; + continue; + } + } + _waitingCandidates[_waitingCounter++] = _candidates[_i]; + } + + _waitingCounter = 0; + for (uint _i = _prioritySlotCounter; _i < _newValidatorCount; _i++) { + _candidates[_i] = _waitingCandidates[_waitingCounter++]; + } + } + /** * @dev Updates the max validator number * @@ -422,6 +485,19 @@ contract RoninValidatorSet is emit MaxValidatorNumberUpdated(_number); } + /** + * @dev Updates the number of reserved slots for prioritized validators + */ + function _setPrioritizedValidatorNumber(uint256 _number) internal { + require( + _number <= _maxValidatorNumber, + "RoninValidatorSet: cannot set number of prioritized greater than number of max validators" + ); + + _maxPrioritizedValidatorNumber = _number; + emit MaxPrioritizedValidatorNumberUpdated(_number); + } + /** * @dev Updates the number of blocks in epoch * diff --git a/src/config.ts b/src/config.ts index 3d9873e3b..c0d32c472 100644 --- a/src/config.ts +++ b/src/config.ts @@ -56,6 +56,7 @@ export interface RoninValidatorSetConf { | { maxValidatorNumber: BigNumberish; maxValidatorCandidate: BigNumberish; + maxPrioritizedValidatorNumber: BigNumberish; numberOfBlocksInEpoch: BigNumberish; numberOfEpochsInPeriod: BigNumberish; } @@ -108,6 +109,7 @@ export const roninValidatorSetConf: RoninValidatorSetConf = { [Network.Hardhat]: undefined, [Network.Devnet]: { maxValidatorNumber: 21, + maxPrioritizedValidatorNumber: 11, maxValidatorCandidate: 100, numberOfBlocksInEpoch: 600, numberOfEpochsInPeriod: 48, // 1 day diff --git a/src/deploy/proxy/ronin-validator-proxy.ts b/src/deploy/proxy/ronin-validator-proxy.ts index 5df6e05eb..a62e3de37 100644 --- a/src/deploy/proxy/ronin-validator-proxy.ts +++ b/src/deploy/proxy/ronin-validator-proxy.ts @@ -17,6 +17,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme initAddress[network.name]!.stakingVestingContract, roninValidatorSetConf[network.name]!.maxValidatorNumber, roninValidatorSetConf[network.name]!.maxValidatorCandidate, + roninValidatorSetConf[network.name]!.maxPrioritizedValidatorNumber, roninValidatorSetConf[network.name]!.numberOfBlocksInEpoch, roninValidatorSetConf[network.name]!.numberOfEpochsInPeriod, ]); diff --git a/test/helpers/ronin-validator-set.ts b/test/helpers/ronin-validator-set.ts index b302dab7b..ce0013b0c 100644 --- a/test/helpers/ronin-validator-set.ts +++ b/test/helpers/ronin-validator-set.ts @@ -102,4 +102,21 @@ export const expects = { 1 ); }, + + emitAddressesPriorityStatusUpdatedEvent: async function ( + tx: ContractTransaction, + expectingAddressList: string[], + expectingPriorityStatusList: boolean[] + ) { + await expectEvent( + contractInterface, + 'AddressesPriorityStatusUpdated', + tx, + (event) => { + expect(event.args[0], 'invalid address list').eql(expectingAddressList); + expect(event.args[1], 'invalid priority status list').eql(expectingPriorityStatusList); + }, + 1 + ); + }, }; diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index 8d8d5d52b..0df4ca396 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -8,8 +8,8 @@ import { SlashIndicator__factory, Staking, Staking__factory, - MockRoninValidatorSetEpochSetter__factory, - MockRoninValidatorSetEpochSetter, + MockRoninValidatorSetExtends__factory, + MockRoninValidatorSetExtends, ProxyAdmin__factory, } from '../../src/types'; import { @@ -27,7 +27,7 @@ import { SlashType } from '../../src/script/slash-indicator'; let slashContract: SlashIndicator; let stakingContract: Staking; -let validatorContract: MockRoninValidatorSetEpochSetter; +let validatorContract: MockRoninValidatorSetExtends; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; @@ -41,6 +41,7 @@ const slashFelonyAmount = BigNumber.from(1); const slashDoubleSignAmount = 1000; const maxValidatorNumber = 3; +const maxPrioritizedValidatorNumber = 0; const numberOfBlocksInEpoch = 600; const numberOfEpochsInPeriod = 48; @@ -69,6 +70,7 @@ describe('[Integration] Slash validators', () => { roninValidatorSetConf[network.name] = { maxValidatorNumber: maxValidatorNumber, maxValidatorCandidate: maxValidatorCandidate, + maxPrioritizedValidatorNumber: maxPrioritizedValidatorNumber, numberOfBlocksInEpoch: numberOfBlocksInEpoch, numberOfEpochsInPeriod: numberOfEpochsInPeriod, }; @@ -97,12 +99,9 @@ describe('[Integration] Slash validators', () => { stakingContract = Staking__factory.connect(stakingContractDeployment.address, deployer); const validatorContractDeployment = await deployments.get('RoninValidatorSetProxy'); - validatorContract = MockRoninValidatorSetEpochSetter__factory.connect( - validatorContractDeployment.address, - deployer - ); + validatorContract = MockRoninValidatorSetExtends__factory.connect(validatorContractDeployment.address, deployer); - const mockValidatorLogic = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy(); + const mockValidatorLogic = await new MockRoninValidatorSetExtends__factory(deployer).deploy(); await mockValidatorLogic.deployed(); const proxyAdminDeployment = await deployments.get('ProxyAdmin'); diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index 754636ebc..de8c1bda3 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -8,8 +8,8 @@ import { SlashIndicator__factory, Staking, Staking__factory, - MockRoninValidatorSetEpochSetter__factory, - MockRoninValidatorSetEpochSetter, + MockRoninValidatorSetExtends__factory, + MockRoninValidatorSetExtends, ProxyAdmin__factory, } from '../../src/types'; import { @@ -25,7 +25,7 @@ import { mineBatchTxs } from '../helpers/utils'; let slashContract: SlashIndicator; let stakingContract: Staking; -let validatorContract: MockRoninValidatorSetEpochSetter; +let validatorContract: MockRoninValidatorSetExtends; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; @@ -39,6 +39,7 @@ const slashFelonyAmount = BigNumber.from(1); const slashDoubleSignAmount = 1000; const maxValidatorNumber = 3; +const maxPrioritizedValidatorNumber = 0; const numberOfBlocksInEpoch = 600; const numberOfEpochsInPeriod = 48; @@ -69,6 +70,7 @@ describe('[Integration] Submit Block Reward', () => { roninValidatorSetConf[network.name] = { maxValidatorNumber: maxValidatorNumber, maxValidatorCandidate: maxValidatorCandidate, + maxPrioritizedValidatorNumber: maxPrioritizedValidatorNumber, numberOfBlocksInEpoch: numberOfBlocksInEpoch, numberOfEpochsInPeriod: numberOfEpochsInPeriod, }; @@ -97,12 +99,9 @@ describe('[Integration] Submit Block Reward', () => { stakingContract = Staking__factory.connect(stakingContractDeployment.address, deployer); const validatorContractDeployment = await deployments.get('RoninValidatorSetProxy'); - validatorContract = MockRoninValidatorSetEpochSetter__factory.connect( - validatorContractDeployment.address, - deployer - ); + validatorContract = MockRoninValidatorSetExtends__factory.connect(validatorContractDeployment.address, deployer); - const mockValidatorLogic = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy(); + const mockValidatorLogic = await new MockRoninValidatorSetExtends__factory(deployer).deploy(); await mockValidatorLogic.deployed(); const proxyAdminDeployment = await deployments.get('ProxyAdmin'); diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index 723e4af8f..cc88b2c72 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -7,8 +7,8 @@ import { SlashIndicator__factory, Staking, Staking__factory, - MockRoninValidatorSetEpochSetter__factory, - MockRoninValidatorSetEpochSetter, + MockRoninValidatorSetExtends__factory, + MockRoninValidatorSetExtends, ProxyAdmin__factory, } from '../../src/types'; import { @@ -27,7 +27,7 @@ import { mineBatchTxs } from '../helpers/utils'; let slashContract: SlashIndicator; let stakingContract: Staking; -let validatorContract: MockRoninValidatorSetEpochSetter; +let validatorContract: MockRoninValidatorSetExtends; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; @@ -41,6 +41,7 @@ const slashFelonyAmount = BigNumber.from(1); const slashDoubleSignAmount = 1000; const maxValidatorNumber = 3; +const maxPrioritizedValidatorNumber = 0; const numberOfBlocksInEpoch = 600; const numberOfEpochsInPeriod = 48; @@ -71,6 +72,7 @@ describe('[Integration] Wrap up epoch', () => { roninValidatorSetConf[network.name] = { maxValidatorNumber: maxValidatorNumber, maxValidatorCandidate: maxValidatorCandidate, + maxPrioritizedValidatorNumber: maxPrioritizedValidatorNumber, numberOfBlocksInEpoch: numberOfBlocksInEpoch, numberOfEpochsInPeriod: numberOfEpochsInPeriod, }; @@ -99,12 +101,9 @@ describe('[Integration] Wrap up epoch', () => { stakingContract = Staking__factory.connect(stakingContractDeployment.address, deployer); const validatorContractDeployment = await deployments.get('RoninValidatorSetProxy'); - validatorContract = MockRoninValidatorSetEpochSetter__factory.connect( - validatorContractDeployment.address, - deployer - ); + validatorContract = MockRoninValidatorSetExtends__factory.connect(validatorContractDeployment.address, deployer); - const mockValidatorLogic = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy(); + const mockValidatorLogic = await new MockRoninValidatorSetExtends__factory(deployer).deploy(); await mockValidatorLogic.deployed(); const proxyAdminDeployment = await deployments.get('ProxyAdmin'); diff --git a/test/integration/Configuration.test.ts b/test/integration/Configuration.test.ts index 92c05b9ff..0407375d4 100644 --- a/test/integration/Configuration.test.ts +++ b/test/integration/Configuration.test.ts @@ -30,15 +30,20 @@ let governanceAdmin: SignerWithAddress; let proxyAdmin: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; +const felonyJailDuration = 28800 * 2; +const misdemeanorThreshold = 10; +const felonyThreshold = 20; const slashFelonyAmount = BigNumber.from(1); const slashDoubleSignAmount = 1000; + const maxValidatorNumber = 4; -const minValidatorBalance = BigNumber.from(100); +const maxPrioritizedValidatorNumber = 0; const numberOfBlocksInEpoch = 600; const numberOfEpochsInPeriod = 48; -const felonyJailDuration = 28800 * 2; -const misdemeanorThreshold = 10; -const felonyThreshold = 20; + +const minValidatorBalance = BigNumber.from(100); +const maxValidatorCandidate = 10; + const bonusPerBlock = BigNumber.from(1); const topUpAmount = BigNumber.from(10000); @@ -60,6 +65,7 @@ describe('[Integration] Configuration check', () => { roninValidatorSetConf[network.name] = { maxValidatorNumber: maxValidatorNumber, maxValidatorCandidate: maxValidatorNumber, + maxPrioritizedValidatorNumber: maxPrioritizedValidatorNumber, numberOfBlocksInEpoch: numberOfBlocksInEpoch, numberOfEpochsInPeriod: numberOfEpochsInPeriod, }; diff --git a/test/validator/RoninValidatorSet-ArrangeValidators.test.ts b/test/validator/RoninValidatorSet-ArrangeValidators.test.ts new file mode 100644 index 000000000..66a1c3ce0 --- /dev/null +++ b/test/validator/RoninValidatorSet-ArrangeValidators.test.ts @@ -0,0 +1,527 @@ +import { expect } from 'chai'; +import { BigNumber } from 'ethers'; +import { ethers } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import * as RoninValidatorSet from '../helpers/ronin-validator-set'; + +import { + Staking, + MockRoninValidatorSetExtends, + MockRoninValidatorSetExtends__factory, + Staking__factory, + TransparentUpgradeableProxyV2__factory, + MockSlashIndicator, + MockSlashIndicator__factory, + StakingVesting__factory, +} from '../../src/types'; +import { Address } from 'hardhat-deploy/dist/types'; + +let validatorContract: MockRoninValidatorSetExtends; +let stakingContract: Staking; +let slashIndicator: MockSlashIndicator; + +let deployer: SignerWithAddress; +let governanceAdmin: SignerWithAddress; +let proxyAdmin: SignerWithAddress; +let validatorCandidates: SignerWithAddress[]; + +const slashFelonyAmount = 100; +const slashDoubleSignAmount = 1000; + +const maxValidatorNumber = 7; +const maxPrioritizedValidatorNumber = 4; +const numberOfBlocksInEpoch = 600; +const numberOfEpochsInPeriod = 48; + +const maxValidatorCandidate = 100; +const minValidatorBalance = BigNumber.from(2); + +const bonusPerBlock = BigNumber.from(1); +const topUpAmount = BigNumber.from(10000); + +const setPriorityStatus = async (addrs: Address[], statuses: boolean[]) => { + return TransparentUpgradeableProxyV2__factory.connect(validatorContract.address, proxyAdmin).functionDelegateCall( + validatorContract.interface.encodeFunctionData('setPrioritizedAddresses', [addrs, statuses]) + ); +}; + +const setPriorityStatusForMany = async (validators: SignerWithAddress[], status: boolean) => { + if (validators.length == 0) { + return; + } + let addrs = validators.map((_) => _.address); + let statuses = new Array(validators.length).fill(status); + await setPriorityStatus(addrs, statuses); +}; + +const setPriorityStatusByIndexes = async (indexes: number[], statuses: boolean[]) => { + expect(indexes.length, 'invalid input for setPriorityStatusByIndex').eq(statuses.length); + let addrs = indexes.filter((_, j) => statuses[j]).map((i) => validatorCandidates[i].address); + statuses = statuses.filter((s) => s == true); + + await setPriorityStatus(addrs, statuses); +}; + +const sortArrayByBoolean = (indexes: number[], statuses: boolean[]) => { + return indexes.sort((a, b) => { + if (statuses[indexes.indexOf(a)] && !statuses[indexes.indexOf(b)]) return -1; + if (!statuses[indexes.indexOf(a)] && statuses[indexes.indexOf(b)]) return 1; + return 0; + }); +}; + +describe('Ronin Validator Set test -- Arrange validators', () => { + before(async () => { + [deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + + const nonce = await deployer.getTransactionCount(); + const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); + const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 6 }); + + /// + /// Deploy staking mock contract + /// + + const stakingVestingLogic = await new StakingVesting__factory(deployer).deploy(); + const stakingVesting = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( + stakingVestingLogic.address, + proxyAdmin.address, + stakingVestingLogic.interface.encodeFunctionData('initialize', [bonusPerBlock, roninValidatorSetAddr]), + { value: topUpAmount } + ); + + /// + /// Deploy slash indicator contract + /// + + slashIndicator = await new MockSlashIndicator__factory(deployer).deploy( + roninValidatorSetAddr, + slashFelonyAmount, + slashDoubleSignAmount + ); + await slashIndicator.deployed(); + + /// + /// Deploy validator mock contract + /// + + const validatorLogicContract = await new MockRoninValidatorSetExtends__factory(deployer).deploy(); + await validatorLogicContract.deployed(); + + const validatorProxyContract = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( + validatorLogicContract.address, + proxyAdmin.address, + validatorLogicContract.interface.encodeFunctionData('initialize', [ + slashIndicator.address, + stakingContractAddr, + stakingVesting.address, + maxValidatorNumber, + maxValidatorCandidate, + maxPrioritizedValidatorNumber, + numberOfBlocksInEpoch, + numberOfEpochsInPeriod, + ]) + ); + await validatorProxyContract.deployed(); + validatorContract = MockRoninValidatorSetExtends__factory.connect(validatorProxyContract.address, deployer); + + /// + /// Deploy staking contract + /// + + const stakingLogicContract = await new Staking__factory(deployer).deploy(); + await stakingLogicContract.deployed(); + + const stakingProxyContract = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( + stakingLogicContract.address, + proxyAdmin.address, + stakingLogicContract.interface.encodeFunctionData('initialize', [roninValidatorSetAddr, minValidatorBalance]) + ); + await stakingProxyContract.deployed(); + stakingContract = Staking__factory.connect(stakingProxyContract.address, deployer); + + expect(roninValidatorSetAddr.toLowerCase(), 'wrong ronin validator set contract address').eq( + validatorContract.address.toLowerCase() + ); + expect(stakingContractAddr.toLowerCase(), 'wrong staking contract address').eq( + stakingContract.address.toLowerCase() + ); + }); + + describe('ValidatorSetContract configuration', async () => { + it('Should config the maxValidatorNumber correctly', async () => { + let _maxValidatorNumber = await validatorContract.maxValidatorNumber(); + expect(_maxValidatorNumber).to.eq(maxValidatorNumber); + }); + + it('Should config the maxPrioritizedValidatorNumber correctly', async () => { + let _maxPrioritizedValidatorNumber = await validatorContract.maxPrioritizedValidatorNumber(); + expect(_maxPrioritizedValidatorNumber).to.eq(maxPrioritizedValidatorNumber); + }); + }); + + describe('Update priority list', async () => { + it('Should be able to add new prioritized validators', async () => { + let addrs = validatorCandidates.slice(0, 10).map((_) => _.address); + let statuses = new Array(10).fill(true); + + let tx = await setPriorityStatus(addrs, statuses); + + await RoninValidatorSet.expects.emitAddressesPriorityStatusUpdatedEvent(tx, addrs, statuses); + }); + + it('Should be able to remove prioritized validators', async () => { + let addrs = validatorCandidates.slice(0, 10).map((_) => _.address); + let statuses = new Array(10).fill(false); + + let tx = await setPriorityStatus(addrs, statuses); + await RoninValidatorSet.expects.emitAddressesPriorityStatusUpdatedEvent(tx, addrs, statuses); + }); + + it('Should be able to add and remove prioritized validators: num(add) > num(remove)', async () => { + let addrs = validatorCandidates.slice(0, 10).map((_) => _.address); + let statuses = new Array(10).fill(true); + let tx = await setPriorityStatus(addrs, statuses); + await RoninValidatorSet.expects.emitAddressesPriorityStatusUpdatedEvent(tx, addrs, statuses); + + addrs = validatorCandidates.slice(4, 7).map((_) => _.address); + statuses = new Array(3).fill(false); + addrs.push(...validatorCandidates.slice(10, 15).map((_) => _.address)); + statuses.push(...new Array(5).fill(true)); + + tx = await setPriorityStatus(addrs, statuses); + await RoninValidatorSet.expects.emitAddressesPriorityStatusUpdatedEvent(tx, addrs, statuses); + }); + + it('Should be able to add and remove prioritized validators: num(add) < num(remove)', async () => { + let addrs = validatorCandidates.slice(0, 15).map((_) => _.address); + let statuses = new Array(15).fill(false); + let tx = await setPriorityStatus(addrs, statuses); + await RoninValidatorSet.expects.emitAddressesPriorityStatusUpdatedEvent(tx, addrs, statuses); + + addrs = validatorCandidates.slice(0, 10).map((_) => _.address); + statuses = new Array(10).fill(true); + tx = await setPriorityStatus(addrs, statuses); + await RoninValidatorSet.expects.emitAddressesPriorityStatusUpdatedEvent(tx, addrs, statuses); + + addrs = validatorCandidates.slice(1, 8).map((_) => _.address); + statuses = new Array(7).fill(false); + addrs.push(...validatorCandidates.slice(10, 14).map((_) => _.address)); + statuses.push(...new Array(4).fill(true)); + tx = await setPriorityStatus(addrs, statuses); + await RoninValidatorSet.expects.emitAddressesPriorityStatusUpdatedEvent(tx, addrs, statuses); + }); + }); + + describe('Arrange validators test', async () => { + const maxRegularValidatorNumber = maxValidatorNumber - maxPrioritizedValidatorNumber; + + beforeEach(async () => { + let validators = validatorCandidates.slice(0, 15); + await setPriorityStatusForMany(validators, false); + }); + + it('Actual(prioritized) == MaxNum(prioritized); Actual(regular) == MaxNum(regular)', async () => { + let actualPrioritizedNumber = maxPrioritizedValidatorNumber; + let actualRegularNumber = maxRegularValidatorNumber; + + let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); + let regularValidators = validatorCandidates.slice( + actualPrioritizedNumber, + actualPrioritizedNumber + actualRegularNumber + ); + + await setPriorityStatusForMany(prioritizedValidators, true); + await setPriorityStatusForMany(regularValidators, false); + + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let expectingValidatorAddrs = [ + ...prioritizedValidators.slice(0, maxPrioritizedValidatorNumber), + ...regularValidators.slice(0, maxRegularValidatorNumber), + ].map((_) => _.address); + + let outputValidators = await validatorContract.arrangeValidatorCandidates( + inputValidatorAddrs, + actualPrioritizedNumber + actualRegularNumber + ); + await expect(outputValidators).eql(expectingValidatorAddrs); + }); + + it('Actual(prioritized) == MaxNum(prioritized); Actual(regular) > MaxNum(regular)', async () => { + let actualPrioritizedNumber = maxPrioritizedValidatorNumber; + let actualRegularNumber = maxRegularValidatorNumber + 10; + + let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); + let regularValidators = validatorCandidates.slice( + actualPrioritizedNumber, + actualPrioritizedNumber + actualRegularNumber + ); + + await setPriorityStatusForMany(prioritizedValidators, true); + await setPriorityStatusForMany(regularValidators, false); + + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let expectingValidatorAddrs = [ + ...prioritizedValidators.slice(0, maxPrioritizedValidatorNumber), + ...regularValidators.slice(0, maxRegularValidatorNumber), + ].map((_) => _.address); + + let outputValidators = await validatorContract.arrangeValidatorCandidates( + inputValidatorAddrs, + maxValidatorNumber + ); + await expect(outputValidators).eql(expectingValidatorAddrs); + }); + + it('Actual(prioritized) == MaxNum(prioritized); Actual(regular) < MaxNum(regular)', async () => { + let actualPrioritizedNumber = maxPrioritizedValidatorNumber; + let actualRegularNumber = maxRegularValidatorNumber - 1; + + let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); + let regularValidators = validatorCandidates.slice( + actualPrioritizedNumber, + actualPrioritizedNumber + actualRegularNumber + ); + + await setPriorityStatusForMany(prioritizedValidators, true); + await setPriorityStatusForMany(regularValidators, false); + + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let expectingValidatorAddrs = [ + ...prioritizedValidators.slice(0, maxPrioritizedValidatorNumber), + ...regularValidators.slice(0, actualRegularNumber), + ].map((_) => _.address); + + let outputValidators = await validatorContract.arrangeValidatorCandidates( + inputValidatorAddrs, + actualPrioritizedNumber + actualRegularNumber + ); + await expect(outputValidators).eql(expectingValidatorAddrs); + }); + + it('Actual(prioritized) > MaxNum(prioritized); Actual(regular) == MaxNum(regular)', async () => { + let actualPrioritizedNumber = maxPrioritizedValidatorNumber + 10; + let actualRegularNumber = maxRegularValidatorNumber; + + let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); + let regularValidators = validatorCandidates.slice( + actualPrioritizedNumber, + actualPrioritizedNumber + actualRegularNumber + ); + + await setPriorityStatusForMany(prioritizedValidators, true); + await setPriorityStatusForMany(regularValidators, false); + + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let expectingValidatorAddrs = [ + ...prioritizedValidators.slice(0, maxPrioritizedValidatorNumber), + ...regularValidators.slice(0, maxRegularValidatorNumber), + ].map((_) => _.address); + + let outputValidators = await validatorContract.arrangeValidatorCandidates( + inputValidatorAddrs, + maxValidatorNumber + ); + await expect(outputValidators).eql(expectingValidatorAddrs); + }); + + it('Actual(prioritized) > MaxNum(prioritized); Actual(regular) > MaxNum(regular)', async () => { + let actualPrioritizedNumber = maxPrioritizedValidatorNumber + 10; + let actualRegularNumber = maxRegularValidatorNumber + 10; + + let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); + let regularValidators = validatorCandidates.slice( + actualPrioritizedNumber, + actualPrioritizedNumber + actualRegularNumber + ); + + await setPriorityStatusForMany(prioritizedValidators, true); + await setPriorityStatusForMany(regularValidators, false); + + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let expectingValidatorAddrs = [ + ...prioritizedValidators.slice(0, maxPrioritizedValidatorNumber), + ...regularValidators.slice(0, maxRegularValidatorNumber), + ].map((_) => _.address); + + let outputValidators = await validatorContract.arrangeValidatorCandidates( + inputValidatorAddrs, + maxValidatorNumber + ); + await expect(outputValidators).eql(expectingValidatorAddrs); + }); + + it('Actual(prioritized) > MaxNum(prioritized); Actual(regular) < MaxNum(regular)', async () => { + let spareSlotNumber = 2; + let actualPrioritizedNumber = maxPrioritizedValidatorNumber + 10; + let actualRegularNumber = maxRegularValidatorNumber - spareSlotNumber; + + let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); + let regularValidators = validatorCandidates.slice( + actualPrioritizedNumber, + actualPrioritizedNumber + actualRegularNumber + ); + + await setPriorityStatusForMany(prioritizedValidators, true); + await setPriorityStatusForMany(regularValidators, false); + + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let expectingValidatorAddrs = [ + ...prioritizedValidators.slice(0, maxPrioritizedValidatorNumber), + ...regularValidators.slice(0, maxRegularValidatorNumber), + ...prioritizedValidators.slice(maxPrioritizedValidatorNumber, maxPrioritizedValidatorNumber + spareSlotNumber), + ].map((_) => _.address); + + let outputValidators = await validatorContract.arrangeValidatorCandidates( + inputValidatorAddrs, + maxValidatorNumber + ); + await expect(outputValidators).eql(expectingValidatorAddrs); + }); + + it('Actual(prioritized) < MaxNum(prioritized); Actual(regular) == MaxNum(regular)', async () => { + let actualPrioritizedNumber = maxPrioritizedValidatorNumber - 1; + let actualRegularNumber = maxRegularValidatorNumber; + + let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); + let regularValidators = validatorCandidates.slice( + actualPrioritizedNumber, + actualPrioritizedNumber + actualRegularNumber + ); + + await setPriorityStatusForMany(prioritizedValidators, true); + await setPriorityStatusForMany(regularValidators, false); + + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let expectingValidatorAddrs = [ + ...prioritizedValidators.slice(0, actualPrioritizedNumber), + ...regularValidators.slice(0, maxRegularValidatorNumber), + ].map((_) => _.address); + + let outputValidators = await validatorContract.arrangeValidatorCandidates( + inputValidatorAddrs, + actualPrioritizedNumber + actualRegularNumber + ); + await expect(outputValidators).eql(expectingValidatorAddrs); + }); + + it('Actual(prioritized) < MaxNum(prioritized); Actual(regular) > MaxNum(regular)', async () => { + let spareSlotNumber = 2; + let actualPrioritizedNumber = maxPrioritizedValidatorNumber - spareSlotNumber; + let actualRegularNumber = maxRegularValidatorNumber + 10; + + let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); + let regularValidators = validatorCandidates.slice( + actualPrioritizedNumber, + actualPrioritizedNumber + actualRegularNumber + ); + + await setPriorityStatusForMany(prioritizedValidators, true); + await setPriorityStatusForMany(regularValidators, false); + + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let expectingValidatorAddrs = [ + ...prioritizedValidators.slice(0, maxPrioritizedValidatorNumber), + ...regularValidators.slice(0, maxRegularValidatorNumber), + ...regularValidators.slice(maxRegularValidatorNumber, maxRegularValidatorNumber + spareSlotNumber), + ].map((_) => _.address); + + let outputValidators = await validatorContract.arrangeValidatorCandidates( + inputValidatorAddrs, + maxValidatorNumber + ); + await expect(outputValidators).eql(expectingValidatorAddrs); + }); + + it('Actual(prioritized) < MaxNum(prioritized); Actual(regular) < MaxNum(regular)', async () => { + let actualPrioritizedNumber = maxPrioritizedValidatorNumber - 2; + let actualRegularNumber = maxRegularValidatorNumber - 2; + + let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); + let regularValidators = validatorCandidates.slice( + actualPrioritizedNumber, + actualPrioritizedNumber + actualRegularNumber + ); + + await setPriorityStatusForMany(prioritizedValidators, true); + await setPriorityStatusForMany(regularValidators, false); + + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let expectingValidatorAddrs = [ + ...prioritizedValidators.slice(0, actualPrioritizedNumber), + ...regularValidators.slice(0, actualRegularNumber), + ].map((_) => _.address); + + let outputValidators = await validatorContract.arrangeValidatorCandidates( + inputValidatorAddrs, + actualPrioritizedNumber + actualRegularNumber + ); + await expect(outputValidators).eql(expectingValidatorAddrs); + }); + }); + + describe('Arrange shuffled validators test', async () => { + beforeEach(async () => { + let validators = validatorCandidates.slice(0, 15); + await setPriorityStatusForMany(validators, false); + }); + + it('Shuffled: Actual(prioritized) == MaxNum(prioritized); Actual(regular) == MaxNum(regular)', async () => { + // prettier-ignore + let indexes = [ 0, 1, 2, 3, 4, 5, 6]; + let statuses = [true, false, true, true, false, true, false]; + + await setPriorityStatusByIndexes(indexes, statuses); + + let sortedIndexes = sortArrayByBoolean(indexes, statuses); + let expectingValidatorAddrs = sortedIndexes.map((i) => validatorCandidates[i].address); + + let inputValidatorAddrs = indexes.map((i) => validatorCandidates[i].address); + let outputValidators = await validatorContract.arrangeValidatorCandidates( + inputValidatorAddrs, + + maxValidatorNumber + ); + await expect(outputValidators).eql(expectingValidatorAddrs); + }); + + it('Shuffled: Actual(prioritized) > MaxNum(prioritized); Actual(regular) < MaxNum(regular)', async () => { + // prettier-ignore + let indexes = [ 0, 1, 2, 3, 4, 5, 6]; + let statuses = [true, false, true, true, false, true, true]; + + await setPriorityStatusByIndexes(indexes, statuses); + + let sortedIndexes = [0, 2, 3, 5, 1, 4, 6]; + let expectingValidatorAddrs = sortedIndexes.map((i) => validatorCandidates[i].address); + + let inputValidatorAddrs = indexes.map((i) => validatorCandidates[i].address); + let outputValidators = await validatorContract.arrangeValidatorCandidates( + inputValidatorAddrs, + + maxValidatorNumber + ); + await expect(outputValidators).eql(expectingValidatorAddrs); + }); + + it('Shuffled: Actual(prioritized) < MaxNum(prioritized); Actual(regular) > MaxNum(regular)', async () => { + // prettier-ignore + let indexes = [ 0, 1, 2, 3, 4, 5, 6, 7]; + let statuses = [true, false, false, false, false, true, true, false]; + + await setPriorityStatusByIndexes(indexes, statuses); + + let sortedIndexes = sortArrayByBoolean(indexes, statuses); + let expectingValidatorAddrs = sortedIndexes + .map((i) => validatorCandidates[i].address) + .slice(0, maxValidatorNumber); + + let inputValidatorAddrs = indexes.map((i) => validatorCandidates[i].address).slice(0, maxValidatorNumber); + let outputValidators = await validatorContract.arrangeValidatorCandidates( + inputValidatorAddrs, + maxValidatorNumber + ); + await expect(outputValidators).eql(expectingValidatorAddrs); + }); + }); +}); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index 003c2a82c..f9ff6b36c 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -5,8 +5,8 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { Staking, - MockRoninValidatorSetEpochSetter, - MockRoninValidatorSetEpochSetter__factory, + MockRoninValidatorSetExtends, + MockRoninValidatorSetExtends__factory, Staking__factory, TransparentUpgradeableProxyV2__factory, MockSlashIndicator, @@ -16,7 +16,7 @@ import { import * as RoninValidatorSet from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../helpers/utils'; -let roninValidatorSet: MockRoninValidatorSetEpochSetter; +let roninValidatorSet: MockRoninValidatorSetExtends; let stakingContract: Staking; let slashIndicator: MockSlashIndicator; @@ -32,6 +32,7 @@ const slashFelonyAmount = 100; const slashDoubleSignAmount = 1000; const maxValidatorNumber = 4; +const maxPrioritizedValidatorNumber = 0; const numberOfBlocksInEpoch = 600; const numberOfEpochsInPeriod = 48; @@ -78,7 +79,7 @@ describe('Ronin Validator Set test', () => { /// Deploy validator mock contract /// - const validatorLogicContract = await new MockRoninValidatorSetEpochSetter__factory(deployer).deploy(); + const validatorLogicContract = await new MockRoninValidatorSetExtends__factory(deployer).deploy(); await validatorLogicContract.deployed(); const validatorProxyContract = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( @@ -90,12 +91,13 @@ describe('Ronin Validator Set test', () => { stakingVesting.address, maxValidatorNumber, maxValidatorCandidate, + maxPrioritizedValidatorNumber, numberOfBlocksInEpoch, numberOfEpochsInPeriod, ]) ); await validatorProxyContract.deployed(); - roninValidatorSet = MockRoninValidatorSetEpochSetter__factory.connect(validatorProxyContract.address, deployer); + roninValidatorSet = MockRoninValidatorSetExtends__factory.connect(validatorProxyContract.address, deployer); /// /// Deploy staking contract From fc62de344fb75207dc92ba03731248099643aa51 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Mon, 26 Sep 2022 11:11:22 +0700 Subject: [PATCH 163/190] Add scheduled maintenance contract (#17) * Add scheduled maintenance contract * Add config setter & update configuration test * Skip maintained validators * Add test * Remove comments * Rename contracts & add deployment script * [SlashIndicator] remove slash function, change minor ABI & adopt for other contracts * Fix test/integration/ActionSlashValidators.test.ts * Fix test/integration/Configuration.test.ts * Update contract name for tests * Fix test compile & fix test/maintainance/Maintenance.test.ts * Fix test/slash/SlashIndicator.test.ts & test/maintainance/Maintenance.test.ts * Fix test/integration/ActionWrapUpEpoch.test.ts * [SlashIndicator] Complete recaling * Optimize gas cost * [Test] Remove all repeated code * Add integration test --- contracts/Maintenance.sol | 168 ++++++++++ contracts/SlashIndicator.sol | 116 ++++--- .../extensions/HasMaintenanceContract.sol | 46 +++ contracts/interfaces/ICandidateManager.sol | 8 + contracts/interfaces/IMaintenance.sol | 106 ++++++ contracts/interfaces/IRoninValidatorSet.sol | 11 +- contracts/interfaces/ISlashIndicator.sol | 50 +-- contracts/interfaces/IStaking.sol | 6 +- .../collections/IHasMaintenanceContract.sol | 25 ++ contracts/libraries/Math.sol | 22 ++ contracts/mocks/MockSlashIndicator.sol | 26 +- contracts/mocks/MockValidatorSet.sol | 6 +- .../mocks/slash/MockValidatorSetForSlash.sol | 27 -- contracts/staking/StakingManager.sol | 23 +- contracts/validator/CandidateManager.sol | 16 +- contracts/validator/RoninValidatorSet.sol | 34 +- src/config.ts | 111 ++++--- src/deploy/calculate-address.ts | 17 +- .../{proxy-admin.ts => logic/maintenance.ts} | 5 +- src/deploy/logic/slash-indicator.ts | 1 - src/deploy/logic/staking-vesting.ts | 1 - src/deploy/logic/staking.ts | 1 - src/deploy/logic/validator-set.ts | 1 - src/deploy/proxy/maintenance-proxy.ts | 32 ++ src/deploy/proxy/ronin-validator-proxy.ts | 14 +- src/deploy/proxy/slash-indicator-proxy.ts | 6 +- src/deploy/proxy/staking-proxy.ts | 5 +- src/deploy/proxy/staking-vesting-proxy.ts | 5 +- src/deploy/verify-address.ts | 30 +- test/helpers/fixture.ts | 133 ++++++++ test/helpers/ronin-validator-set.ts | 19 -- test/helpers/slash-indicator.ts | 23 -- .../integration/ActionSlashValidators.test.ts | 178 ++++------ test/integration/ActionSubmitReward.test.ts | 114 ++----- test/integration/ActionWrapUpEpoch.test.ts | 164 ++++------ test/integration/Configuration.test.ts | 136 ++++---- test/maintainance/Maintenance.test.ts | 304 ++++++++++++++++++ test/slash/SlashIndicator.test.ts | 247 +++++++------- test/staking/Staking.test.ts | 25 +- ...oninValidatorSet-ArrangeValidators.test.ts | 15 +- test/validator/RoninValidatorSet.test.ts | 33 +- 41 files changed, 1545 insertions(+), 765 deletions(-) create mode 100644 contracts/Maintenance.sol create mode 100644 contracts/extensions/HasMaintenanceContract.sol create mode 100644 contracts/interfaces/IMaintenance.sol create mode 100644 contracts/interfaces/collections/IHasMaintenanceContract.sol delete mode 100644 contracts/mocks/slash/MockValidatorSetForSlash.sol rename src/deploy/{proxy-admin.ts => logic/maintenance.ts} (74%) create mode 100644 src/deploy/proxy/maintenance-proxy.ts create mode 100644 test/helpers/fixture.ts delete mode 100644 test/helpers/slash-indicator.ts create mode 100644 test/maintainance/Maintenance.test.ts diff --git a/contracts/Maintenance.sol b/contracts/Maintenance.sol new file mode 100644 index 000000000..d0b4703b5 --- /dev/null +++ b/contracts/Maintenance.sol @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "./interfaces/IMaintenance.sol"; +import "./interfaces/IRoninValidatorSet.sol"; +import "./interfaces/IStaking.sol"; +import "./extensions/HasValidatorContract.sol"; +import "./libraries/Math.sol"; + +contract Maintenance is IMaintenance, HasValidatorContract, Initializable { + using Math for uint256; + + /// @dev Mapping from consensus address => maintenance schedule + mapping(address => Schedule) internal _schedule; + + /// @dev The min block period to maintenance + uint256 public minMaintenanceBlockPeriod; + /// @dev The max block period to maintenance + uint256 public maxMaintenanceBlockPeriod; + /// @dev The min blocks from the current block to the start block + uint256 public minOffset; + /// @dev The max number of scheduled maintenances + uint256 public maxSchedules; + + constructor() { + _disableInitializers(); + } + + /** + * @dev Initializes the contract storage. + */ + function initialize( + address __validatorContract, + uint256 _minMaintenanceBlockPeriod, + uint256 _maxMaintenanceBlockPeriod, + uint256 _minOffset, + uint256 _maxSchedules + ) external initializer { + _setValidatorContract(__validatorContract); + _setMaintenanceConfig(_minMaintenanceBlockPeriod, _maxMaintenanceBlockPeriod, _minOffset, _maxSchedules); + } + + /** + * @inheritdoc IMaintenance + */ + function setMaintenanceConfig( + uint256 _minMaintenanceBlockPeriod, + uint256 _maxMaintenanceBlockPeriod, + uint256 _minOffset, + uint256 _maxSchedules + ) external onlyAdmin { + _setMaintenanceConfig(_minMaintenanceBlockPeriod, _maxMaintenanceBlockPeriod, _minOffset, _maxSchedules); + } + + /** + * @inheritdoc IMaintenance + */ + function schedule( + address _consensusAddr, + uint256 _startedAtBlock, + uint256 _endedAtBlock + ) external override { + IRoninValidatorSet _validator = _validatorContract; + + require(_validator.isValidator(_consensusAddr), "Maintenance: consensus address must be a validator"); + require( + _validator.isCandidateAdmin(_consensusAddr, msg.sender), + "Maintenance: method caller must be a candidate admin" + ); + require(!scheduled(_consensusAddr), "Maintenance: already scheduled"); + require(totalSchedules() < maxSchedules, "Maintenance: exceeds total of schedules"); + require(block.number + minOffset <= _startedAtBlock, "Maintenance: invalid offset size"); + require(_startedAtBlock < _endedAtBlock, "Maintenance: start block must be less than end block"); + uint256 _blockPeriod = _endedAtBlock - _startedAtBlock; + require( + _blockPeriod.inRange(minMaintenanceBlockPeriod, maxMaintenanceBlockPeriod), + "Maintenance: invalid maintenance block period" + ); + require(_validator.epochEndingAt(_startedAtBlock - 1), "Maintenance: start block is not at the start of an epoch"); + require(_validator.epochEndingAt(_endedAtBlock), "Maintenance: end block is not at the end of an epoch"); + + Schedule storage _sSchedule = _schedule[_consensusAddr]; + uint256 _period = _validator.periodOf(block.number); + require( + _period > _validator.periodOf(_sSchedule.lastUpdatedBlock) && _period > _validator.periodOf(_sSchedule.to), + "Maintenance: schedule twice in a period is not allowed" + ); + + _sSchedule.from = _startedAtBlock; + _sSchedule.to = _endedAtBlock; + _sSchedule.lastUpdatedBlock = block.number; + emit MaintenanceScheduled(_consensusAddr, _sSchedule); + } + + /** + * @inheritdoc IMaintenance + */ + function getSchedule(address _consensusAddr) external view returns (Schedule memory) { + return _schedule[_consensusAddr]; + } + + /** + * @inheritdoc IMaintenance + */ + function bulkMaintaining(address[] calldata _addrList, uint256 _block) + external + view + override + returns (bool[] memory _resList) + { + _resList = new bool[](_addrList.length); + for (uint _i = 0; _i < _addrList.length; _i++) { + _resList[_i] = maintaining(_addrList[_i], _block); + } + } + + /** + * @inheritdoc IMaintenance + */ + function totalSchedules() public view returns (uint256 _count) { + address[] memory _validators = _validatorContract.getValidators(); + for (uint _i = 0; _i < _validators.length; _i++) { + if (scheduled(_validators[_i])) { + _count++; + } + } + } + + /** + * @inheritdoc IMaintenance + */ + function maintaining(address _consensusAddr, uint256 _block) public view returns (bool) { + Schedule storage _s = _schedule[_consensusAddr]; + return _s.from <= _block && _block <= _s.to; + } + + /** + * @inheritdoc IMaintenance + */ + function scheduled(address _consensusAddr) public view override returns (bool) { + return block.number <= _schedule[_consensusAddr].to; + } + + /** + * @dev Sets the min block period and max block period to maintenance. + * + * Requirements: + * - The max period is larger than the min period. + * + * Emits the event `MaintenanceConfigUpdated`. + * + */ + function _setMaintenanceConfig( + uint256 _minMaintenanceBlockPeriod, + uint256 _maxMaintenanceBlockPeriod, + uint256 _minOffset, + uint256 _maxSchedules + ) internal { + require(_minMaintenanceBlockPeriod < _maxMaintenanceBlockPeriod, "Maintenance: invalid block periods"); + minMaintenanceBlockPeriod = _minMaintenanceBlockPeriod; + maxMaintenanceBlockPeriod = _maxMaintenanceBlockPeriod; + minOffset = _minOffset; + maxSchedules = _maxSchedules; + emit MaintenanceConfigUpdated(_minMaintenanceBlockPeriod, _maxMaintenanceBlockPeriod, _minOffset, _maxSchedules); + } +} diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index 09fdd3500..c78e051af 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -5,10 +5,17 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "./interfaces/ISlashIndicator.sol"; import "./extensions/HasValidatorContract.sol"; +import "./extensions/HasMaintenanceContract.sol"; +import "./libraries/Math.sol"; + +contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenanceContract, Initializable { + using Math for uint256; + + /// @dev Mapping from validator address => period index => unavailability indicator + mapping(address => mapping(uint256 => uint256)) internal _unavailabilityIndicator; + /// @dev Maping from validator address => period index => slash type + mapping(address => mapping(uint256 => SlashType)) internal _unavailabilitySlashed; -contract SlashIndicator is ISlashIndicator, HasValidatorContract, Initializable { - /// @dev Mapping from validator address => unavailability indicator - mapping(address => uint256) internal _unavailabilityIndicator; /// @dev The last block that a validator is slashed uint256 public lastSlashedBlock; @@ -25,7 +32,7 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, Initializable uint256 public felonyJailDuration; modifier onlyCoinbase() { - require(msg.sender == block.coinbase, "SlashIndicator: method caller is not the coinbase"); + require(msg.sender == block.coinbase, "SlashIndicator: method caller must be coinbase"); _; } @@ -47,6 +54,7 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, Initializable */ function initialize( address __validatorContract, + address __maintenanceContract, uint256 _misdemeanorThreshold, uint256 _felonyThreshold, uint256 _slashFelonyAmount, @@ -54,6 +62,7 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, Initializable uint256 _felonyJailBlocks ) external initializer { _setValidatorContract(__validatorContract); + _setMaintenanceContract(__maintenanceContract); _setSlashThresholds(_felonyThreshold, _misdemeanorThreshold); _setSlashFelonyAmount(_slashFelonyAmount); _setSlashDoubleSignAmount(_slashDoubleSignAmount); @@ -68,23 +77,31 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, Initializable * @inheritdoc ISlashIndicator */ function slash(address _validatorAddr) external override onlyCoinbase oncePerBlock { - if (msg.sender == _validatorAddr) { + if (msg.sender == _validatorAddr || _maintenanceContract.maintaining(_validatorAddr, block.number)) { return; } - uint256 _count = ++_unavailabilityIndicator[_validatorAddr]; + uint256 _period = _validatorContract.periodOf(block.number); + uint256 _count = ++_unavailabilityIndicator[_validatorAddr][_period]; + (uint256 _misdemeanorThreshold, uint256 _felonyThreshold) = unavailabilityThresholdsOf( + _validatorAddr, + block.number + ); - // Slashes the validator as either the fenoly or the misdemeanor - if (_count == felonyThreshold) { - emit ValidatorSlashed(_validatorAddr, SlashType.FELONY); + SlashType _slashType = getUnavailabilitySlashType(_validatorAddr, _period); + + if (_count >= _felonyThreshold && _slashType < SlashType.FELONY) { + _unavailabilitySlashed[_validatorAddr][_period] = SlashType.FELONY; + emit UnavailabilitySlashed(_validatorAddr, SlashType.FELONY, _period); _validatorContract.slash(_validatorAddr, block.number + felonyJailDuration, slashFelonyAmount); - delete _unavailabilityIndicator[_validatorAddr]; - address[] memory _addrList = new address[](1); - _addrList[0] = _validatorAddr; - emit UnavailabilityIndicatorsReset(_addrList); - } else if (_count == misdemeanorThreshold) { - emit ValidatorSlashed(_validatorAddr, SlashType.MISDEMEANOR); + return; + } + + if (_count >= _misdemeanorThreshold && _slashType < SlashType.MISDEMEANOR) { + _unavailabilitySlashed[_validatorAddr][_period] = SlashType.MISDEMEANOR; + emit UnavailabilitySlashed(_validatorAddr, SlashType.MISDEMEANOR, _period); _validatorContract.slash(_validatorAddr, 0, 0); + return; } } @@ -101,27 +118,6 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, Initializable } } - /** - * @inheritdoc ISlashIndicator - */ - function resetCounters(address[] calldata _validatorAddrs) external override onlyValidatorContract { - _resetCounters(_validatorAddrs); - } - - /** - * @dev Resets counter for the validator address. - */ - function _resetCounters(address[] calldata _validatorAddrs) private { - if (_validatorAddrs.length == 0) { - return; - } - - for (uint _i = 0; _i < _validatorAddrs.length; _i++) { - delete _unavailabilityIndicator[_validatorAddrs[_i]]; - } - emit UnavailabilityIndicatorsReset(_validatorAddrs); - } - /////////////////////////////////////////////////////////////////////////////////////// // GOVERNANCE FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// @@ -161,17 +157,59 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, Initializable /** * @inheritdoc ISlashIndicator */ - function getSlashIndicator(address validator) external view override returns (uint256) { - return _unavailabilityIndicator[validator]; + function getUnavailabilitySlashType(address _validatorAddr, uint256 _period) public view returns (SlashType) { + return _unavailabilitySlashed[_validatorAddr][_period]; + } + + /** + * @inheritdoc ISlashIndicator + */ + function unavailabilityThresholdsOf(address _addr, uint256 _block) + public + view + returns (uint256 _misdemeanorThreshold, uint256 _felonyThreshold) + { + uint256 _blockLength = _validatorContract.numberOfBlocksInEpoch() * _validatorContract.numberOfEpochsInPeriod(); + uint256 _start = (_block / _blockLength) * _blockLength; + uint256 _end = _start + _blockLength - 1; + IMaintenance.Schedule memory _s = _maintenanceContract.getSchedule(_addr); + + bool _fromInRange = _s.from.inRange(_start, _end); + bool _toInRange = _s.to.inRange(_start, _end); + uint256 _availableDuration = _blockLength; + if (_fromInRange && _toInRange) { + _availableDuration -= _s.to - _s.from + 1; + } else if (_fromInRange) { + _availableDuration -= _end - _s.from + 1; + } else if (_toInRange) { + _availableDuration -= _s.to - _start + 1; + } + + _misdemeanorThreshold = misdemeanorThreshold.scale(_availableDuration, _blockLength); + _felonyThreshold = felonyThreshold.scale(_availableDuration, _blockLength); } /** * @inheritdoc ISlashIndicator */ - function getSlashThresholds() external view override returns (uint256, uint256) { + function currentUnavailabilityIndicator(address _validator) external view override returns (uint256) { + return getUnavailabilityIndicator(_validator, _validatorContract.periodOf(block.number)); + } + + /** + * @inheritdoc ISlashIndicator + */ + function getUnavailabilityThresholds() external view override returns (uint256, uint256) { return (misdemeanorThreshold, felonyThreshold); } + /** + * @inheritdoc ISlashIndicator + */ + function getUnavailabilityIndicator(address _validator, uint256 _period) public view override returns (uint256) { + return _unavailabilityIndicator[_validator][_period]; + } + /////////////////////////////////////////////////////////////////////////////////////// // HELPER FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// diff --git a/contracts/extensions/HasMaintenanceContract.sol b/contracts/extensions/HasMaintenanceContract.sol new file mode 100644 index 000000000..389afc79d --- /dev/null +++ b/contracts/extensions/HasMaintenanceContract.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./HasProxyAdmin.sol"; +import "../interfaces/collections/IHasMaintenanceContract.sol"; +import "../interfaces/IMaintenance.sol"; + +contract HasMaintenanceContract is IHasMaintenanceContract, HasProxyAdmin { + IMaintenance internal _maintenanceContract; + + modifier onlyMaintenanceContract() { + require( + maintenanceContract() == msg.sender, + "HasMaintenanceContract: method caller must be scheduled maintenance contract" + ); + _; + } + + /** + * @inheritdoc IHasMaintenanceContract + */ + function maintenanceContract() public view override returns (address) { + return address(_maintenanceContract); + } + + /** + * @inheritdoc IHasMaintenanceContract + */ + function setMaintenanceContract(address _addr) external override onlyAdmin { + _setMaintenanceContract(_addr); + } + + /** + * @dev Sets the scheduled maintenance contract. + * + * Requirements: + * - The new address is a contract. + * + * Emits the event `MaintenanceContractUpdated`. + * + */ + function _setMaintenanceContract(address _addr) internal { + _maintenanceContract = IMaintenance(_addr); + emit MaintenanceContractUpdated(_addr); + } +} diff --git a/contracts/interfaces/ICandidateManager.sol b/contracts/interfaces/ICandidateManager.sol index c113b712b..5b840c635 100644 --- a/contracts/interfaces/ICandidateManager.sol +++ b/contracts/interfaces/ICandidateManager.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.9; interface ICandidateManager { struct ValidatorCandidate { + // Admin of the candidate + address admin; // Address of the validator that produces block, e.g. block.coinbase. This is so-called validator address. address consensusAddr; // Address that receives mining reward of the validator @@ -52,6 +54,7 @@ interface ICandidateManager { * */ function addValidatorCandidate( + address _admin, address _consensusAddr, address payable _treasuryAddr, uint256 _commissionRate @@ -80,4 +83,9 @@ interface ICandidateManager { * @dev Returns candidates info. */ function getCandidateInfos() external view returns (ValidatorCandidate[] memory); + + /** + * @dev Returns whether the address is the candidate admin. + */ + function isCandidateAdmin(address _candidate, address _admin) external view returns (bool); } diff --git a/contracts/interfaces/IMaintenance.sol b/contracts/interfaces/IMaintenance.sol new file mode 100644 index 000000000..22198dd5f --- /dev/null +++ b/contracts/interfaces/IMaintenance.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IMaintenance { + struct Schedule { + uint256 from; + uint256 to; + uint256 lastUpdatedBlock; + } + + /// @dev Emitted when the maintenance is scheduled. + event MaintenanceScheduled(address consensusAddr, Schedule); + /// @dev Emitted when the maintenance config is updated. + event MaintenanceConfigUpdated( + uint256 minMaintenanceBlockPeriod, + uint256 maxMaintenanceBlockPeriod, + uint256 minOffset, + uint256 maxSchedules + ); + + /** + * @dev Returns whether the validator `_consensusAddr` is maintaining at the block number `_block`. + */ + function maintaining(address _consensusAddr, uint256 _block) external view returns (bool); + + /** + * @dev Returns the bool array indicating the validator is maintaining or not. + */ + function bulkMaintaining(address[] calldata _addrList, uint256 _block) external view returns (bool[] memory); + + /** + * @dev Returns whether the validator `_consensusAddr` has scheduled. + */ + function scheduled(address _consensusAddr) external view returns (bool); + + /** + * @dev Returns the detailed schedule of the validator `_consensusAddr`. + */ + function getSchedule(address _consensusAddr) external view returns (Schedule memory); + + /** + * @dev Returns the min block period for maintenance. + */ + function minMaintenanceBlockPeriod() external view returns (uint256); + + /** + * @dev Returns the max block period for maintenance. + */ + function maxMaintenanceBlockPeriod() external view returns (uint256); + + /** + * @dev Sets the min block period and max block period for maintenance. + * + * Requirements: + * - The method caller is admin. + * - The max period is larger than the min period. + * + * Emits the event `MaintenanceConfigUpdated`. + * + */ + function setMaintenanceConfig( + uint256 _minMaintenanceBlockPeriod, + uint256 _maxMaintenanceBlockPeriod, + uint256 _minOffset, + uint256 _maxSchedules + ) external; + + /** + * @dev Returns the min blocks from current block to the maintenance start block. + */ + function minOffset() external view returns (uint256); + + /** + * @dev Returns the max number of scheduled maintenances. + */ + function maxSchedules() external view returns (uint256); + + /** + * @dev Returns the total of current schedules. + */ + function totalSchedules() external view returns (uint256 _count); + + /** + * @dev Schedules for maintenance from `_startedAtBlock` to `_startedAtBlock`. + * + * Requirements: + * - The candidate `_consensusAddr` is the validator. + * - The method caller is candidate admin of the candidate `_consensusAddr`. + * - The candidate `_consensusAddr` has no schedule yet or the previous is done. + * - The total number of schedules is not larger than `maxSchedules()`. + * - The start block must be at least `minOffset()` blocks from the current block. + * - The end block is larger than the start block. + * - The scheduled block period is larger than the `minMaintenanceBlockPeriod()` and less than the `maxMaintenanceBlockPeriod()`. + * - The start block is at the start of an epoch. + * - The end block is at the end of an epoch. + * + * Emits the event `MaintenanceScheduled`. + * + */ + function schedule( + address _consensusAddr, + uint256 _startedAtBlock, + uint256 _endedAtBlock + ) external; +} diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index 437fed91b..5c2e092c4 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -19,8 +19,8 @@ interface IRoninValidatorSet is ICandidateManager { event RewardDeprecated(address coinbaseAddr, uint256 rewardAmount); /// @dev Emitted when the block reward is submitted. event BlockRewardSubmitted(address coinbaseAddr, uint256 submittedAmount, uint256 bonusAmount); - /// @dev Emitted when the validator is slashed. - event ValidatorSlashed(address validatorAddr, uint256 jailedUntil, uint256 deductedStakingAmount); + /// @dev Emitted when the validator is punished. + event ValidatorPunished(address validatorAddr, uint256 jailedUntil, uint256 deductedStakingAmount); /// @dev Emitted when the validator reward is distributed. event MiningRewardDistributed(address validatorAddr, uint256 amount); /// @dev Emitted when the amount of RON reward is distributed. @@ -74,7 +74,7 @@ interface IRoninValidatorSet is ICandidateManager { * Requirements: * - The method caller is slash indicator contract. * - * Emits the event `ValidatorSlashed`. + * Emits the event `ValidatorPunished`. * */ function slash( @@ -132,6 +132,11 @@ interface IRoninValidatorSet is ICandidateManager { */ function getValidators() external view returns (address[] memory); + /** + * @dev Returns whether the address is validator or not. + */ + function isValidator(address _addr) external view returns (bool); + /** * @dev Returns whether the epoch ending is at the block number `_block`. */ diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index 9ee2ac7ff..aa78c365b 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -2,13 +2,9 @@ pragma solidity ^0.8.9; -import "../interfaces/IRoninValidatorSet.sol"; - interface ISlashIndicator { - /// @dev Emitted when the validator is slashed - event ValidatorSlashed(address indexed validator, SlashType slashType); - /// @dev Emitted when the validator indicators are reset - event UnavailabilityIndicatorsReset(address[] validators); + /// @dev Emitted when the validator is slashed for unavailability + event UnavailabilitySlashed(address indexed validator, SlashType slashType, uint256 period); /// @dev Emitted when the thresholds updated event SlashThresholdsUpdated(uint256 felonyThreshold, uint256 misdemeanorThreshold); /// @dev Emitted when the amount of slashing felony updated @@ -32,22 +28,11 @@ interface ISlashIndicator { * Requirements: * - Only coinbase can call this method * - * Emits the event `ValidatorSlashed` + * Emits the event `UnavailabilitySlashed` when the threshold is reached. * */ function slash(address _valAddr) external; - /** - * @dev Resets the counter of all validators at the end of every period - * - * Requirements: - * - Only validator contract can call this method - * - * Emits the `UnavailabilityIndicatorsReset` events - * - */ - function resetCounters(address[] calldata) external; - /** * @dev Slashes for double signing * @@ -102,12 +87,33 @@ interface ISlashIndicator { function setFelonyJailDuration(uint256 _felonyJailDuration) external; /** - * @dev Gets slash indicator of a validator + * @dev Returns the current unavailability indicator of a validator. + */ + function currentUnavailabilityIndicator(address _validator) external view returns (uint256); + + /** + * @dev Returns the scaled thresholds based on the maintenance duration for unavailability slashing. + */ + function unavailabilityThresholdsOf(address _addr, uint256 _block) + external + view + returns (uint256 _misdemeanorThreshold, uint256 _felonyThreshold); + + /** + * @dev Retursn the unavailability indicator in the period `_period` of a validator. + */ + function getUnavailabilityIndicator(address _validator, uint256 _period) external view returns (uint256); + + /** + * @dev Gets the unavailability thresholds. */ - function getSlashIndicator(address _validator) external view returns (uint256); + function getUnavailabilityThresholds() + external + view + returns (uint256 _misdemeanorThreshold, uint256 _felonyThreshold); /** - * @dev Gets the slash thresholds + * @dev Checks the slashed tier for unavailability of a validator. */ - function getSlashThresholds() external view returns (uint256 misdemeanorThreshold, uint256 felonyThreshold); + function getUnavailabilitySlashType(address _validatorAddr, uint256 _period) external view returns (SlashType); } diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 1795d5cc2..38ae5559c 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -112,14 +112,18 @@ interface IStaking is IRewardPool { * @dev Proposes a candidate to become a validator. * * Requirements: - * - The candidate admin is able to receive RON. + * - The method caller is able to receive RON. * - The treasury is able to receive RON. * - The amount is larger than or equal to the minimum validator balance `minValidatorBalance()`. * * Emits the event `ValidatorPoolAdded`. * + * @param _candidateAdmin the candidate admin will be stored in the validator contract, used for calling function that affects + * to its candidate. IE: scheduling maintenance. + * */ function proposeValidator( + address _candidateAdmin, address _consensusAddr, address payable _treasuryAddr, uint256 _commissionRate diff --git a/contracts/interfaces/collections/IHasMaintenanceContract.sol b/contracts/interfaces/collections/IHasMaintenanceContract.sol new file mode 100644 index 000000000..604d213fd --- /dev/null +++ b/contracts/interfaces/collections/IHasMaintenanceContract.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IHasMaintenanceContract { + /// @dev Emitted when the maintenance contract is updated. + event MaintenanceContractUpdated(address); + + /** + * @dev Returns the maintenance contract. + */ + function maintenanceContract() external view returns (address); + + /** + * @dev Sets the maintenance contract. + * + * Requirements: + * - The method caller is admin. + * - The new address is a contract. + * + * Emits the event `MaintenanceContractUpdated`. + * + */ + function setMaintenanceContract(address) external; +} diff --git a/contracts/libraries/Math.sol b/contracts/libraries/Math.sol index 92d6f2044..fd3bd85f8 100644 --- a/contracts/libraries/Math.sol +++ b/contracts/libraries/Math.sol @@ -16,4 +16,26 @@ library Math { function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } + + /** + * @dev Returns whether the number `c` is in range of [a; b]. + */ + function inRange( + uint256 c, + uint256 a, + uint256 b + ) internal pure returns (bool) { + return a <= c && c <= b; + } + + /** + * @dev Returns the result from scaling c to ratio 1-a/b. + */ + function scale( + uint256 c, + uint256 a, + uint256 b + ) internal pure returns (uint256) { + return (c * a) / b; + } } diff --git a/contracts/mocks/MockSlashIndicator.sol b/contracts/mocks/MockSlashIndicator.sol index a5d68a465..bc2382ae6 100644 --- a/contracts/mocks/MockSlashIndicator.sol +++ b/contracts/mocks/MockSlashIndicator.sol @@ -11,7 +11,7 @@ contract MockSlashIndicator is ISlashIndicator { uint256 public slashDoubleSignAmount; modifier onlyCoinbase() { - require(msg.sender == block.coinbase, "SlashIndicator: method caller is not the coinbase"); + require(msg.sender == block.coinbase, "SlashIndicator: method caller must be coinbase"); _; } @@ -33,15 +33,11 @@ contract MockSlashIndicator is ISlashIndicator { validatorContract.slash(_validatorAddr, 0, 0); } - function resetCounters(address[] calldata) external {} - function slash(address _valAddr) external override {} function slashDoubleSign(address _valAddr, bytes calldata _evidence) external override {} - function getSlashIndicator(address _validator) external view override returns (uint256) {} - - function getSlashThresholds() + function getUnavailabilityThresholds() external view override @@ -55,4 +51,22 @@ contract MockSlashIndicator is ISlashIndicator { function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external override {} function setFelonyJailDuration(uint256 _felonyJailDuration) external override {} + + function currentUnavailabilityIndicator(address _validator) external view override returns (uint256) {} + + function getUnavailabilityIndicator(address _validator, uint256 _period) external view override returns (uint256) {} + + function getUnavailabilitySlashType(address _validatorAddr, uint256 _period) + external + view + override + returns (SlashType) + {} + + function unavailabilityThresholdsOf(address _addr, uint256 _block) + external + view + override + returns (uint256 _felonyThreshold, uint256 _misdemeanorThreshold) + {} } diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index 34047baf6..dec511480 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -90,10 +90,6 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { uint256 _slashAmount ) external override {} - function resetCounters(address[] calldata _validatorAddrs) external { - ISlashIndicator(slashIndicatorContract).resetCounters(_validatorAddrs); - } - function setMaxValidatorNumber(uint256 _maxValidatorNumber) external override {} function setNumberOfBlocksInEpoch(uint256 _numberOfBlocksInEpoch) external override {} @@ -115,4 +111,6 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { {} function getPriorityStatus(address _addr) external view override returns (bool) {} + + function isValidator(address _addr) external view override returns (bool) {} } diff --git a/contracts/mocks/slash/MockValidatorSetForSlash.sol b/contracts/mocks/slash/MockValidatorSetForSlash.sol deleted file mode 100644 index b8de57779..000000000 --- a/contracts/mocks/slash/MockValidatorSetForSlash.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -import "../../interfaces/ISlashIndicator.sol"; - -contract MockValidatorSetForSlash { - ISlashIndicator private _slashingContract; - - function _setSlashingContract() internal view virtual returns (ISlashIndicator) { - return _slashingContract; - } - - function setSlashingContract(ISlashIndicator _addr) external { - _slashingContract = _addr; - } - - function slash( - address _validatorAddr, - uint256 _newJailedUntil, - uint256 _slashMisdemeanor - ) external {} - - function resetCounters(address[] calldata _addr) external { - _slashingContract.resetCounters(_addr); - } -} diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 99318709b..3c977dcd4 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -78,19 +78,20 @@ abstract contract StakingManager is * @inheritdoc IStaking */ function proposeValidator( + address _candidateAdmin, address _consensusAddr, address payable _treasuryAddr, uint256 _commissionRate ) external payable override nonReentrant { uint256 _amount = msg.value; - address payable _candidateAdmin = payable(msg.sender); - _proposeValidator(_candidateAdmin, _consensusAddr, _treasuryAddr, _commissionRate, _amount); + address payable _poolAdmin = payable(msg.sender); + _proposeValidator(_poolAdmin, _candidateAdmin, _consensusAddr, _treasuryAddr, _commissionRate, _amount); PoolDetail storage _pool = _stakingPool[_consensusAddr]; - _pool.admin = _candidateAdmin; + _pool.admin = _poolAdmin; _pool.addr = _consensusAddr; - _stake(_stakingPool[_consensusAddr], _candidateAdmin, _amount); - emit ValidatorPoolAdded(_consensusAddr, _candidateAdmin); + _stake(_stakingPool[_consensusAddr], _poolAdmin, _amount); + emit ValidatorPoolAdded(_consensusAddr, _poolAdmin); } /** @@ -130,23 +131,27 @@ abstract contract StakingManager is * @dev Proposes a candidate to become a valdiator. * * Requirements: - * - The candidate admin is able to receive RON. + * - The pool admin is able to receive RON. * - The treasury is able to receive RON. * - The amount is larger than or equal to the minimum validator balance `minValidatorBalance()`. * + * @param _candidateAdmin the candidate admin will be stored in the validator contract, used for calling function that affects + * to its candidate. IE: scheduling maintenance. + * */ function _proposeValidator( - address payable _candidateAdmin, + address payable _poolAdmin, + address _candidateAdmin, address _consensusAddr, address payable _treasuryAddr, uint256 _commissionRate, uint256 _amount ) internal { - require(_sendRON(_candidateAdmin, 0), "StakingManager: pool admin cannot receive RON"); + require(_sendRON(_poolAdmin, 0), "StakingManager: pool admin cannot receive RON"); require(_sendRON(_treasuryAddr, 0), "StakingManager: treasury cannot receive RON"); require(_amount >= minValidatorBalance(), "StakingManager: insufficient amount"); - _validatorContract.addValidatorCandidate(_consensusAddr, _treasuryAddr, _commissionRate); + _validatorContract.addValidatorCandidate(_candidateAdmin, _consensusAddr, _treasuryAddr, _commissionRate); } /** diff --git a/contracts/validator/CandidateManager.sol b/contracts/validator/CandidateManager.sol index 54d61cb44..9200351ce 100644 --- a/contracts/validator/CandidateManager.sol +++ b/contracts/validator/CandidateManager.sol @@ -36,6 +36,7 @@ contract CandidateManager is ICandidateManager, HasStakingContract { * @inheritdoc ICandidateManager */ function addValidatorCandidate( + address _admin, address _consensusAddr, address payable _treasuryAddr, uint256 _commissionRate @@ -46,7 +47,13 @@ contract CandidateManager is ICandidateManager, HasStakingContract { _candidateIndex[_consensusAddr] = ~_length; _candidates.push(_consensusAddr); - _candidateInfo[_consensusAddr] = ValidatorCandidate(_consensusAddr, _treasuryAddr, _commissionRate, new bytes(0)); + _candidateInfo[_consensusAddr] = ValidatorCandidate( + _admin, + _consensusAddr, + _treasuryAddr, + _commissionRate, + new bytes(0) + ); emit ValidatorCandidateAdded(_consensusAddr, _treasuryAddr, _candidateIndex[_consensusAddr]); } @@ -98,6 +105,13 @@ contract CandidateManager is ICandidateManager, HasStakingContract { return _candidates; } + /** + * @inheritdoc ICandidateManager + */ + function isCandidateAdmin(address _candidate, address _admin) external view override returns (bool) { + return _candidateInfo[_candidate].admin == _admin; + } + /** * @dev Removes the candidate. */ diff --git a/contracts/validator/RoninValidatorSet.sol b/contracts/validator/RoninValidatorSet.sol index 560f81396..e9b87c56e 100644 --- a/contracts/validator/RoninValidatorSet.sol +++ b/contracts/validator/RoninValidatorSet.sol @@ -8,6 +8,7 @@ import "../extensions/RONTransferHelper.sol"; import "../extensions/HasStakingVestingContract.sol"; import "../extensions/HasStakingContract.sol"; import "../extensions/HasSlashIndicatorContract.sol"; +import "../extensions/HasMaintenanceContract.sol"; import "../interfaces/IRoninValidatorSet.sol"; import "../libraries/Sorting.sol"; import "../libraries/Math.sol"; @@ -19,6 +20,7 @@ contract RoninValidatorSet is HasStakingContract, HasStakingVestingContract, HasSlashIndicatorContract, + HasMaintenanceContract, CandidateManager, Initializable { @@ -82,6 +84,7 @@ contract RoninValidatorSet is address __slashIndicatorContract, address __stakingContract, address __stakingVestingContract, + address __maintenanceContract, uint256 __maxValidatorNumber, uint256 __maxValidatorCandidate, uint256 __maxPrioritizedValidatorNumber, @@ -91,6 +94,7 @@ contract RoninValidatorSet is _setSlashIndicatorContract(__slashIndicatorContract); _setStakingContract(__stakingContract); _setStakingVestingContract(__stakingVestingContract); + _setMaintenanceContract(__maintenanceContract); _setMaxValidatorNumber(__maxValidatorNumber); _setMaxValidatorCandidate(__maxValidatorCandidate); _setPrioritizedValidatorNumber(__maxPrioritizedValidatorNumber); @@ -114,7 +118,7 @@ contract RoninValidatorSet is address _coinbaseAddr = msg.sender; // Deprecates reward for non-validator or slashed validator if ( - !_isValidator(_coinbaseAddr) || _jailed(_coinbaseAddr) || _rewardDeprecated(_coinbaseAddr, periodOf(block.number)) + !isValidator(_coinbaseAddr) || _jailed(_coinbaseAddr) || _rewardDeprecated(_coinbaseAddr, periodOf(block.number)) ) { emit RewardDeprecated(_coinbaseAddr, _submittedReward); return; @@ -173,8 +177,6 @@ contract RoninValidatorSet is } if (_periodEnding) { - ISlashIndicator(_slashIndicatorContract).resetCounters(_validators); - _staking.settleRewardPools(_validators); if (_delegatingAmount > 0) { require( @@ -220,7 +222,7 @@ contract RoninValidatorSet is IStaking(_stakingContract).deductStakingAmount(_validatorAddr, _slashAmount); } - emit ValidatorSlashed(_validatorAddr, _jailedUntil[_validatorAddr], _slashAmount); + emit ValidatorPunished(_validatorAddr, _jailedUntil[_validatorAddr], _slashAmount); } /** @@ -274,6 +276,13 @@ contract RoninValidatorSet is } } + /** + * @inheritdoc IRoninValidatorSet + */ + function isValidator(address _addr) public view returns (bool) { + return _validatorMap[_addr]; + } + /** * @inheritdoc IRoninValidatorSet */ @@ -413,17 +422,26 @@ contract RoninValidatorSet is delete _validatorMap[_validator[_i]]; } + uint256 _count; + bool[] memory _maintainingList = _maintenanceContract.bulkMaintaining(_candidates, block.number + 1); for (uint256 _i = 0; _i < _newValidatorCount; _i++) { + if (_maintainingList[_i]) { + continue; + } + address _newValidator = _candidates[_i]; - if (_newValidator == _validator[_i]) { + if (_newValidator == _validator[_count]) { + _count++; continue; } - delete _validatorMap[_validator[_i]]; + + delete _validatorMap[_validator[_count]]; _validatorMap[_newValidator] = true; - _validator[_i] = _newValidator; + _validator[_count] = _newValidator; + _count++; } - validatorCount = _newValidatorCount; + validatorCount = _count; emit ValidatorSetUpdated(_candidates); } diff --git a/src/config.ts b/src/config.ts index c0d32c472..169400caa 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,6 @@ import { BigNumber, BigNumberish } from 'ethers'; import { ethers } from 'hardhat'; +import { Address } from 'hardhat-deploy/dist/types'; export enum Network { Hardhat = 'hardhat', @@ -14,63 +15,89 @@ export const defaultAddress = '0x0000000000000000000000000000000000000000'; export interface InitAddr { [network: LiteralNetwork]: { - governanceAdmin: string; - validatorContract?: string; - stakingContract?: string; - stakingVestingContract?: string; - slashIndicator?: string; + governanceAdmin: Address; + maintenanceContract?: Address; + stakingVestingContract?: Address; + slashIndicatorContract?: Address; + stakingContract?: Address; + validatorContract?: Address; }; } +export interface MaintenanceArguments { + minMaintenanceBlockPeriod?: BigNumberish; + maxMaintenanceBlockPeriod?: BigNumberish; + minOffset?: BigNumberish; + maxSchedules?: BigNumberish; +} +export interface MaintenanceConfig { + [network: LiteralNetwork]: MaintenanceArguments | undefined; +} + +export interface StakingArguments { + minValidatorBalance?: BigNumberish; +} + +export interface StakingConfig { + [network: LiteralNetwork]: StakingArguments | undefined; +} + +export interface StakingVestingArguments { + bonusPerBlock?: BigNumberish; + topupAmount?: BigNumberish; +} + +export interface StakingVestingConfig { + [network: LiteralNetwork]: StakingVestingArguments | undefined; +} -export interface StakingConf { - [network: LiteralNetwork]: - | { - minValidatorBalance: BigNumberish; - } - | undefined; +export interface SlashIndicatorArguments { + misdemeanorThreshold?: BigNumberish; + felonyThreshold?: BigNumberish; + slashFelonyAmount?: BigNumberish; + slashDoubleSignAmount?: BigNumberish; + felonyJailBlocks?: BigNumberish; } -export interface StakingVestingConf { - [network: LiteralNetwork]: - | { - bonusPerBlock: BigNumberish; - topupAmount: BigNumberish; - } - | undefined; +export interface SlashIndicatorConfig { + [network: LiteralNetwork]: SlashIndicatorArguments | undefined; } -export interface SlashIndicatorConf { - [network: LiteralNetwork]: - | { - misdemeanorThreshold: BigNumberish; - felonyThreshold: BigNumberish; - slashFelonyAmount: BigNumberish; - slashDoubleSignAmount: BigNumberish; - felonyJailBlocks: BigNumberish; - } - | undefined; +export interface RoninValidatorSetArguments { + maxValidatorNumber?: BigNumberish; + maxValidatorCandidate?: BigNumberish; + maxPrioritizedValidatorNumber?: BigNumberish; + numberOfBlocksInEpoch?: BigNumberish; + numberOfEpochsInPeriod?: BigNumberish; } -export interface RoninValidatorSetConf { - [network: LiteralNetwork]: - | { - maxValidatorNumber: BigNumberish; - maxValidatorCandidate: BigNumberish; - maxPrioritizedValidatorNumber: BigNumberish; - numberOfBlocksInEpoch: BigNumberish; - numberOfEpochsInPeriod: BigNumberish; - } - | undefined; +export interface RoninValidatorSetConfig { + [network: LiteralNetwork]: RoninValidatorSetArguments | undefined; } export const initAddress: InitAddr = { + [Network.Hardhat]: { + governanceAdmin: ethers.constants.AddressZero, + }, [Network.Devnet]: { governanceAdmin: '0x93b8eed0a1e082ae2f478fd7f8c14b1fc0261bb1', }, }; // TODO: update config for testnet & mainnet -export const stakingConfig: StakingConf = { +export const maintenanceConf: MaintenanceConfig = { + [Network.Hardhat]: undefined, + [Network.Devnet]: { + minMaintenanceBlockPeriod: 600, // 600 blocks + maxMaintenanceBlockPeriod: 28800, // ~1 day + minOffset: 28800, // requests before maintaining at least ~1 day + maxSchedules: 3, // only 3 schedules are happening|in the futures + }, + [Network.Testnet]: undefined, + [Network.Mainnet]: undefined, +}; + +// TODO: update config for testnet & mainnet +export const stakingConfig: StakingConfig = { [Network.Hardhat]: undefined, [Network.Devnet]: { minValidatorBalance: BigNumber.from(10).pow(18).mul(BigNumber.from(10).pow(5)), // 100.000 RON @@ -80,7 +107,7 @@ export const stakingConfig: StakingConf = { }; // TODO: update config for testnet & mainnet -export const stakingVestingConfig: StakingVestingConf = { +export const stakingVestingConfig: StakingVestingConfig = { [Network.Hardhat]: undefined, [Network.Devnet]: { bonusPerBlock: BigNumber.from(10).pow(18), // 1 RON per block @@ -91,7 +118,7 @@ export const stakingVestingConfig: StakingVestingConf = { }; // TODO: update config for testnet & mainnet -export const slashIndicatorConf: SlashIndicatorConf = { +export const slashIndicatorConf: SlashIndicatorConfig = { [Network.Hardhat]: undefined, [Network.Devnet]: { misdemeanorThreshold: 50, @@ -105,7 +132,7 @@ export const slashIndicatorConf: SlashIndicatorConf = { }; // TODO: update config for testnet & mainnet -export const roninValidatorSetConf: RoninValidatorSetConf = { +export const roninValidatorSetConf: RoninValidatorSetConfig = { [Network.Hardhat]: undefined, [Network.Devnet]: { maxValidatorNumber: 21, diff --git a/src/deploy/calculate-address.ts b/src/deploy/calculate-address.ts index 3ed087ab0..02d8fa1c0 100644 --- a/src/deploy/calculate-address.ts +++ b/src/deploy/calculate-address.ts @@ -6,22 +6,29 @@ import { initAddress } from '../config'; const deploy = async ({ getNamedAccounts }: HardhatRuntimeEnvironment) => { const { deployer } = await getNamedAccounts(); let nonce = await ethers.provider.getTransactionCount(deployer); - initAddress[network.name].slashIndicator = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++ }); - initAddress[network.name].stakingContract = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++ }); + initAddress[network.name].maintenanceContract = ethers.utils.getContractAddress({ + from: deployer, + nonce: nonce++, + }); initAddress[network.name].stakingVestingContract = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++, }); + initAddress[network.name].slashIndicatorContract = ethers.utils.getContractAddress({ + from: deployer, + nonce: nonce++, + }); + initAddress[network.name].stakingContract = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++ }); initAddress[network.name].validatorContract = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++ }); }; deploy.tags = ['CalculateAddresses']; deploy.dependencies = [ - 'ProxyAdmin', - 'StakingLogic', + 'MaintenanceLogic', + 'StakingVestingLogic', 'SlashIndicatorLogic', + 'StakingLogic', 'RoninValidatorSetLogic', - 'StakingVestingLogic', ]; export default deploy; diff --git a/src/deploy/proxy-admin.ts b/src/deploy/logic/maintenance.ts similarity index 74% rename from src/deploy/proxy-admin.ts rename to src/deploy/logic/maintenance.ts index 781cf2f9e..ea2ff7ae8 100644 --- a/src/deploy/proxy-admin.ts +++ b/src/deploy/logic/maintenance.ts @@ -4,12 +4,13 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const { deploy } = deployments; const { deployer } = await getNamedAccounts(); - await deploy('ProxyAdmin', { + await deploy('MaintenanceLogic', { + contract: 'Maintenance', from: deployer, log: true, }); }; -deploy.tags = ['ProxyAdmin']; +deploy.tags = ['MaintenanceLogic']; export default deploy; diff --git a/src/deploy/logic/slash-indicator.ts b/src/deploy/logic/slash-indicator.ts index 2c1890bd5..1a4fbcb4d 100644 --- a/src/deploy/logic/slash-indicator.ts +++ b/src/deploy/logic/slash-indicator.ts @@ -12,6 +12,5 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme }; deploy.tags = ['SlashIndicatorLogic']; -deploy.dependencies = ['ProxyAdmin']; export default deploy; diff --git a/src/deploy/logic/staking-vesting.ts b/src/deploy/logic/staking-vesting.ts index 68198bd0d..423377664 100644 --- a/src/deploy/logic/staking-vesting.ts +++ b/src/deploy/logic/staking-vesting.ts @@ -12,6 +12,5 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme }; deploy.tags = ['StakingVestingLogic']; -deploy.dependencies = ['ProxyAdmin']; export default deploy; diff --git a/src/deploy/logic/staking.ts b/src/deploy/logic/staking.ts index bf15d0b0f..7199cfff7 100644 --- a/src/deploy/logic/staking.ts +++ b/src/deploy/logic/staking.ts @@ -12,6 +12,5 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme }; deploy.tags = ['StakingLogic']; -deploy.dependencies = ['ProxyAdmin']; export default deploy; diff --git a/src/deploy/logic/validator-set.ts b/src/deploy/logic/validator-set.ts index dc66019ae..a741db6ac 100644 --- a/src/deploy/logic/validator-set.ts +++ b/src/deploy/logic/validator-set.ts @@ -12,6 +12,5 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme }; deploy.tags = ['RoninValidatorSetLogic']; -deploy.dependencies = ['ProxyAdmin']; export default deploy; diff --git a/src/deploy/proxy/maintenance-proxy.ts b/src/deploy/proxy/maintenance-proxy.ts new file mode 100644 index 000000000..22d48cf55 --- /dev/null +++ b/src/deploy/proxy/maintenance-proxy.ts @@ -0,0 +1,32 @@ +import { network } from 'hardhat'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +import { maintenanceConf, initAddress } from '../../config'; +import { Maintenance__factory } from '../../types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + const logicContract = await deployments.get('MaintenanceLogic'); + + const data = new Maintenance__factory().interface.encodeFunctionData('initialize', [ + initAddress[network.name]!.validatorContract, + maintenanceConf[network.name]!.minMaintenanceBlockPeriod, + maintenanceConf[network.name]!.maxMaintenanceBlockPeriod, + maintenanceConf[network.name]!.minOffset, + maintenanceConf[network.name]!.maxSchedules, + ]); + + await deploy('MaintenanceProxy', { + contract: 'TransparentUpgradeableProxyV2', + from: deployer, + log: true, + args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], + }); +}; + +deploy.tags = ['MaintenanceProxy']; +deploy.dependencies = ['MaintenanceLogic']; + +export default deploy; diff --git a/src/deploy/proxy/ronin-validator-proxy.ts b/src/deploy/proxy/ronin-validator-proxy.ts index a62e3de37..0994d261e 100644 --- a/src/deploy/proxy/ronin-validator-proxy.ts +++ b/src/deploy/proxy/ronin-validator-proxy.ts @@ -8,13 +8,13 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const { deploy } = deployments; const { deployer } = await getNamedAccounts(); - const proxyAdmin = await deployments.get('ProxyAdmin'); const logicContract = await deployments.get('RoninValidatorSetLogic'); const data = new RoninValidatorSet__factory().interface.encodeFunctionData('initialize', [ - initAddress[network.name]!.slashIndicator, + initAddress[network.name]!.slashIndicatorContract, initAddress[network.name]!.stakingContract, initAddress[network.name]!.stakingVestingContract, + initAddress[network.name]!.maintenanceContract, roninValidatorSetConf[network.name]!.maxValidatorNumber, roninValidatorSetConf[network.name]!.maxValidatorCandidate, roninValidatorSetConf[network.name]!.maxPrioritizedValidatorNumber, @@ -26,17 +26,11 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, proxyAdmin.address, data], + args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], }); }; deploy.tags = ['RoninValidatorSetProxy']; -deploy.dependencies = [ - 'ProxyAdmin', - 'RoninValidatorSetLogic', - 'SlashIndicatorProxy', - 'StakingProxy', - 'StakingVestingProxy', -]; +deploy.dependencies = ['RoninValidatorSetLogic', 'StakingProxy']; export default deploy; diff --git a/src/deploy/proxy/slash-indicator-proxy.ts b/src/deploy/proxy/slash-indicator-proxy.ts index 94dfaa747..4aadb8e9e 100644 --- a/src/deploy/proxy/slash-indicator-proxy.ts +++ b/src/deploy/proxy/slash-indicator-proxy.ts @@ -8,11 +8,11 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const { deploy } = deployments; const { deployer } = await getNamedAccounts(); - const proxyAdmin = await deployments.get('ProxyAdmin'); const logicContract = await deployments.get('SlashIndicatorLogic'); const data = new SlashIndicator__factory().interface.encodeFunctionData('initialize', [ initAddress[network.name]!.validatorContract, + initAddress[network.name]!.maintenanceContract, slashIndicatorConf[network.name]!.misdemeanorThreshold, slashIndicatorConf[network.name]!.felonyThreshold, slashIndicatorConf[network.name]!.slashFelonyAmount, @@ -24,11 +24,11 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, proxyAdmin.address, data], + args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], }); }; deploy.tags = ['SlashIndicatorProxy']; -deploy.dependencies = ['ProxyAdmin', 'SlashIndicatorLogic']; +deploy.dependencies = ['SlashIndicatorLogic', 'StakingVestingProxy']; export default deploy; diff --git a/src/deploy/proxy/staking-proxy.ts b/src/deploy/proxy/staking-proxy.ts index fa050eb8b..14e0cc539 100644 --- a/src/deploy/proxy/staking-proxy.ts +++ b/src/deploy/proxy/staking-proxy.ts @@ -8,7 +8,6 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const { deploy } = deployments; const { deployer } = await getNamedAccounts(); - const proxyAdmin = await deployments.get('ProxyAdmin'); const logicContract = await deployments.get('StakingLogic'); const data = new Staking__factory().interface.encodeFunctionData('initialize', [ @@ -20,11 +19,11 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, proxyAdmin.address, data], + args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], }); }; deploy.tags = ['StakingProxy']; -deploy.dependencies = ['ProxyAdmin', 'StakingLogic', 'SlashIndicatorProxy']; +deploy.dependencies = ['StakingLogic', 'SlashIndicatorProxy']; export default deploy; diff --git a/src/deploy/proxy/staking-vesting-proxy.ts b/src/deploy/proxy/staking-vesting-proxy.ts index 49cd6acf6..b74825c85 100644 --- a/src/deploy/proxy/staking-vesting-proxy.ts +++ b/src/deploy/proxy/staking-vesting-proxy.ts @@ -9,7 +9,6 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const { deploy } = deployments; const { deployer } = await getNamedAccounts(); - const proxyAdmin = await deployments.get('ProxyAdmin'); const logicContract = await deployments.get('StakingVestingLogic'); const data = new StakingVesting__factory().interface.encodeFunctionData('initialize', [ @@ -21,12 +20,12 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, proxyAdmin.address, data], + args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], value: BigNumber.from(stakingVestingConfig[network.name]!.topupAmount), }); }; deploy.tags = ['StakingVestingProxy']; -deploy.dependencies = ['ProxyAdmin', 'RoninValidatorSetLogic', 'SlashIndicatorProxy', 'StakingProxy']; +deploy.dependencies = ['StakingVestingLogic', 'MaintenanceProxy']; export default deploy; diff --git a/src/deploy/verify-address.ts b/src/deploy/verify-address.ts index 1ae4b0742..846834919 100644 --- a/src/deploy/verify-address.ts +++ b/src/deploy/verify-address.ts @@ -4,23 +4,24 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { initAddress } from '../config'; const deploy = async ({ deployments }: HardhatRuntimeEnvironment) => { - const indicator = await deployments.get('SlashIndicatorProxy'); - const stakingContract = await deployments.get('StakingProxy'); + const MaintenanceContract = await deployments.get('MaintenanceProxy'); const stakingVestingContract = await deployments.get('StakingVestingProxy'); + const slashIndicatorContract = await deployments.get('SlashIndicatorProxy'); + const stakingContract = await deployments.get('StakingProxy'); const validatorContract = await deployments.get('RoninValidatorSetProxy'); - if (initAddress[network.name].slashIndicator?.toLowerCase() != indicator.address.toLowerCase()) { + if (initAddress[network.name].maintenanceContract?.toLowerCase() != MaintenanceContract.address.toLowerCase()) { throw Error( `invalid address for indicator, expected=${initAddress[ network.name - ].slashIndicator?.toLowerCase()}, actual=${indicator.address.toLowerCase()}` + ].maintenanceContract?.toLowerCase()}, actual=${MaintenanceContract.address.toLowerCase()}` ); } - if (initAddress[network.name].stakingContract?.toLowerCase() != stakingContract.address.toLowerCase()) { + if (initAddress[network.name].slashIndicatorContract?.toLowerCase() != slashIndicatorContract.address.toLowerCase()) { throw Error( - `invalid address for stakingContract, expected=${initAddress[ + `invalid address for slashIndicator, expected=${initAddress[ network.name - ].stakingContract?.toLowerCase()}, actual=${stakingContract.address.toLowerCase()}` + ].slashIndicatorContract?.toLowerCase()}, actual=${slashIndicatorContract.address.toLowerCase()}` ); } if (initAddress[network.name].stakingVestingContract?.toLowerCase() != stakingVestingContract.address.toLowerCase()) { @@ -30,6 +31,13 @@ const deploy = async ({ deployments }: HardhatRuntimeEnvironment) => { ].stakingVestingContract?.toLowerCase()}, actual=${stakingVestingContract.address.toLowerCase()}` ); } + if (initAddress[network.name].stakingContract?.toLowerCase() != stakingContract.address.toLowerCase()) { + throw Error( + `invalid address for stakingContract, expected=${initAddress[ + network.name + ].stakingContract?.toLowerCase()}, actual=${stakingContract.address.toLowerCase()}` + ); + } if (initAddress[network.name].validatorContract?.toLowerCase() != validatorContract.address.toLowerCase()) { throw Error( `invalid address for validatorContract, expected=${initAddress[ @@ -41,12 +49,6 @@ const deploy = async ({ deployments }: HardhatRuntimeEnvironment) => { }; deploy.tags = ['VerifyAddress']; -deploy.dependencies = [ - 'ProxyAdmin', - 'StakingProxy', - 'SlashIndicatorProxy', - 'StakingVestingProxy', - 'RoninValidatorSetProxy', -]; +deploy.dependencies = ['StakingProxy', 'SlashIndicatorProxy', 'StakingVestingProxy', 'RoninValidatorSetProxy']; export default deploy; diff --git a/test/helpers/fixture.ts b/test/helpers/fixture.ts new file mode 100644 index 000000000..e8780ec65 --- /dev/null +++ b/test/helpers/fixture.ts @@ -0,0 +1,133 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { BigNumber, BytesLike } from 'ethers'; +import { deployments, ethers, network } from 'hardhat'; +import { Address } from 'hardhat-deploy/dist/types'; +import { + initAddress, + MaintenanceArguments, + maintenanceConf, + Network, + RoninValidatorSetArguments, + roninValidatorSetConf, + SlashIndicatorArguments, + slashIndicatorConf, + StakingArguments, + stakingConfig, + StakingVestingArguments, + stakingVestingConfig, +} from '../../src/config'; +import { TransparentUpgradeableProxyV2__factory } from '../../src/types'; + +export interface InitTestOutput { + maintenanceContractAddress: Address; + slashContractAddress: Address; + stakingContractAddress: Address; + stakingVestingContractAddress: Address; + validatorContractAddress: Address; +} + +export const defaultTestConfig = { + felonyJailBlocks: 28800 * 2, + misdemeanorThreshold: 5, + felonyThreshold: 10, + slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), + slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), + + maxValidatorNumber: 4, + maxPrioritizedValidatorNumber: 0, + numberOfBlocksInEpoch: 600, + numberOfEpochsInPeriod: 48, + + minValidatorBalance: BigNumber.from(100), + maxValidatorCandidate: 10, + + bonusPerBlock: BigNumber.from(1), + topupAmount: BigNumber.from(10000), + minMaintenanceBlockPeriod: 100, + maxMaintenanceBlockPeriod: 1000, + minOffset: 200, + maxSchedules: 2, +}; + +export const initTest = (id: string) => + deployments.createFixture< + InitTestOutput, + MaintenanceArguments & + StakingArguments & + StakingVestingArguments & + SlashIndicatorArguments & + RoninValidatorSetArguments & { governanceAdmin: Address } + >(async ({ deployments }, options) => { + if (network.name == Network.Hardhat) { + initAddress[network.name] = { governanceAdmin: options?.governanceAdmin ?? ethers.constants.AddressZero }; + maintenanceConf[network.name] = { + minMaintenanceBlockPeriod: options?.minMaintenanceBlockPeriod ?? defaultTestConfig.minMaintenanceBlockPeriod, + maxMaintenanceBlockPeriod: options?.maxMaintenanceBlockPeriod ?? defaultTestConfig.maxMaintenanceBlockPeriod, + minOffset: options?.minOffset ?? defaultTestConfig.minOffset, + maxSchedules: options?.maxSchedules ?? defaultTestConfig.maxSchedules, + }; + slashIndicatorConf[network.name] = { + misdemeanorThreshold: options?.misdemeanorThreshold ?? defaultTestConfig.misdemeanorThreshold, + felonyThreshold: options?.felonyThreshold ?? defaultTestConfig.felonyThreshold, + slashFelonyAmount: options?.slashFelonyAmount ?? defaultTestConfig.slashFelonyAmount, + slashDoubleSignAmount: options?.slashDoubleSignAmount ?? defaultTestConfig.slashDoubleSignAmount, + felonyJailBlocks: options?.felonyJailBlocks ?? defaultTestConfig.felonyJailBlocks, + }; + roninValidatorSetConf[network.name] = { + maxValidatorNumber: options?.maxValidatorNumber ?? defaultTestConfig.maxValidatorNumber, + maxValidatorCandidate: options?.maxValidatorCandidate ?? defaultTestConfig.maxValidatorCandidate, + maxPrioritizedValidatorNumber: + options?.maxPrioritizedValidatorNumber ?? defaultTestConfig.maxPrioritizedValidatorNumber, + numberOfBlocksInEpoch: options?.numberOfBlocksInEpoch ?? defaultTestConfig.numberOfBlocksInEpoch, + numberOfEpochsInPeriod: options?.numberOfEpochsInPeriod ?? defaultTestConfig.numberOfEpochsInPeriod, + }; + stakingConfig[network.name] = { + minValidatorBalance: options?.minValidatorBalance ?? defaultTestConfig.minValidatorBalance, + }; + stakingVestingConfig[network.name] = { + bonusPerBlock: options?.bonusPerBlock ?? defaultTestConfig.bonusPerBlock, + topupAmount: options?.topupAmount ?? defaultTestConfig.topupAmount, + }; + } + + await deployments.fixture([ + 'CalculateAddresses', + 'RoninValidatorSetProxy', + 'SlashIndicatorProxy', + 'StakingProxy', + 'MaintenanceProxy', + 'StakingVestingProxy', + id, + ]); + + const maintenanceContractDeployment = await deployments.get('MaintenanceProxy'); + const slashContractDeployment = await deployments.get('SlashIndicatorProxy'); + const stakingContractDeployment = await deployments.get('StakingProxy'); + const stakingVestingContractDeployment = await deployments.get('StakingVestingProxy'); + const validatorContractDeployment = await deployments.get('RoninValidatorSetProxy'); + + return { + maintenanceContractAddress: maintenanceContractDeployment.address, + slashContractAddress: slashContractDeployment.address, + stakingContractAddress: stakingContractDeployment.address, + stakingVestingContractAddress: stakingVestingContractDeployment.address, + validatorContractAddress: validatorContractDeployment.address, + }; + }, id); + +export class GovernanceAdminInterface { + signer!: SignerWithAddress; + address = ethers.constants.AddressZero; + constructor(signer: SignerWithAddress) { + this.signer = signer; + this.address = signer.address; + } + + functionDelegateCall(to: Address, data: BytesLike) { + return TransparentUpgradeableProxyV2__factory.connect(to, this.signer).functionDelegateCall(data); + } + + upgrade(from: Address, to: Address) { + return TransparentUpgradeableProxyV2__factory.connect(from, this.signer).upgradeTo(to); + } +} diff --git a/test/helpers/ronin-validator-set.ts b/test/helpers/ronin-validator-set.ts index ce0013b0c..8a411f164 100644 --- a/test/helpers/ronin-validator-set.ts +++ b/test/helpers/ronin-validator-set.ts @@ -43,25 +43,6 @@ export const expects = { ); }, - emitValidatorSlashedEvent: async function ( - tx: ContractTransaction, - expectingValidatorAddr: string, - expectingJailedUntil: BigNumberish, - expectingDeductedStakingAmount: BigNumberish - ) { - await expectEvent( - contractInterface, - 'ValidatorSlashed', - tx, - (event) => { - expect(event.args[0], 'invalid coinbase address').eq(expectingValidatorAddr); - expect(event.args[1], 'invalid jailed until').eq(expectingJailedUntil); - expect(event.args[2], 'invalid deducted staking amount').eq(expectingDeductedStakingAmount); - }, - 1 - ); - }, - emitMiningRewardDistributedEvent: async function ( tx: ContractTransaction, expectingCoinbaseAddr: string, diff --git a/test/helpers/slash-indicator.ts b/test/helpers/slash-indicator.ts deleted file mode 100644 index 329b23b24..000000000 --- a/test/helpers/slash-indicator.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { expect } from 'chai'; -import { BigNumberish, ContractTransaction } from 'ethers'; - -import { expectEvent } from './utils'; -import { ISlashIndicator__factory } from '../../src/types'; - -const contractInterface = ISlashIndicator__factory.createInterface(); - -export const expects = { - emitUnavailabilityIndicatorsResetEvent: async function (tx: ContractTransaction, expectingValidatorList?: string[]) { - await expectEvent( - contractInterface, - 'UnavailabilityIndicatorsReset', - tx, - (event) => { - if (expectingValidatorList) { - expect(event.args[0], 'invalid reset validator list').eql(expectingValidatorList); - } - }, - 1 - ); - }, -}; diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index 0df4ca396..a83cd3a4a 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -1,7 +1,8 @@ import { expect } from 'chai'; -import { network, ethers, deployments, getNamedAccounts } from 'hardhat'; +import { network, ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { Address } from 'hardhat-deploy/dist/types'; +import { BigNumber, BigNumberish, ContractTransaction } from 'ethers'; import { SlashIndicator, @@ -10,136 +11,71 @@ import { Staking__factory, MockRoninValidatorSetExtends__factory, MockRoninValidatorSetExtends, - ProxyAdmin__factory, } from '../../src/types'; -import { - Network, - slashIndicatorConf, - roninValidatorSetConf, - stakingConfig, - stakingVestingConfig, - initAddress, -} from '../../src/config'; -import { BigNumber, ContractTransaction } from 'ethers'; + import { expects as RoninValidatorSetExpects } from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../helpers/utils'; import { SlashType } from '../../src/script/slash-indicator'; +import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; let slashContract: SlashIndicator; let stakingContract: Staking; let validatorContract: MockRoninValidatorSetExtends; +let governanceAdmin: GovernanceAdminInterface; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; -let governanceAdmin: SignerWithAddress; +let governor: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; -const felonyJailDuration = 28800 * 2; +const felonyJailBlocks = 28800 * 2; const misdemeanorThreshold = 10; const felonyThreshold = 20; const slashFelonyAmount = BigNumber.from(1); const slashDoubleSignAmount = 1000; - -const maxValidatorNumber = 3; -const maxPrioritizedValidatorNumber = 0; +const minValidatorBalance = BigNumber.from(100); const numberOfBlocksInEpoch = 600; const numberOfEpochsInPeriod = 48; -const minValidatorBalance = BigNumber.from(100); -const maxValidatorCandidate = 10; - -const bonusPerBlock = BigNumber.from(1); -const topUpAmount = BigNumber.from(10000); - describe('[Integration] Slash validators', () => { before(async () => { - [deployer, coinbase, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + [deployer, coinbase, governor, ...validatorCandidates] = await ethers.getSigners(); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + governanceAdmin = new GovernanceAdminInterface(governor); + + const { slashContractAddress, stakingContractAddress, validatorContractAddress } = await initTest( + 'ActionSlashValidators' + )({ + felonyJailBlocks, + misdemeanorThreshold, + felonyThreshold, + slashFelonyAmount, + slashDoubleSignAmount, + minValidatorBalance, + governanceAdmin: governanceAdmin.address, + }); - if (network.name == Network.Hardhat) { - initAddress[network.name] = { - governanceAdmin: governanceAdmin.address, - }; - slashIndicatorConf[network.name] = { - misdemeanorThreshold: misdemeanorThreshold, - felonyThreshold: felonyThreshold, - slashFelonyAmount: slashFelonyAmount, - slashDoubleSignAmount: slashDoubleSignAmount, - felonyJailBlocks: felonyJailDuration, - }; - roninValidatorSetConf[network.name] = { - maxValidatorNumber: maxValidatorNumber, - maxValidatorCandidate: maxValidatorCandidate, - maxPrioritizedValidatorNumber: maxPrioritizedValidatorNumber, - numberOfBlocksInEpoch: numberOfBlocksInEpoch, - numberOfEpochsInPeriod: numberOfEpochsInPeriod, - }; - stakingConfig[network.name] = { - minValidatorBalance: minValidatorBalance, - }; - stakingVestingConfig[network.name] = { - bonusPerBlock: bonusPerBlock, - topupAmount: topUpAmount, - }; - } - - await deployments.fixture([ - 'ProxyAdmin', - 'CalculateAddresses', - 'RoninValidatorSetProxy', - 'SlashIndicatorProxy', - 'StakingProxy', - 'StakingVestingProxy', - ]); - - const slashContractDeployment = await deployments.get('SlashIndicatorProxy'); - slashContract = SlashIndicator__factory.connect(slashContractDeployment.address, deployer); - - const stakingContractDeployment = await deployments.get('StakingProxy'); - stakingContract = Staking__factory.connect(stakingContractDeployment.address, deployer); - - const validatorContractDeployment = await deployments.get('RoninValidatorSetProxy'); - validatorContract = MockRoninValidatorSetExtends__factory.connect(validatorContractDeployment.address, deployer); + slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); + stakingContract = Staking__factory.connect(stakingContractAddress, deployer); + validatorContract = MockRoninValidatorSetExtends__factory.connect(validatorContractAddress, deployer); const mockValidatorLogic = await new MockRoninValidatorSetExtends__factory(deployer).deploy(); await mockValidatorLogic.deployed(); - - const proxyAdminDeployment = await deployments.get('ProxyAdmin'); - let proxyAdminContract = ProxyAdmin__factory.connect(proxyAdminDeployment.address, deployer); - - await proxyAdminContract.upgrade(validatorContract.address, mockValidatorLogic.address); - }); - - describe('Configuration test', async () => { - describe('ValidatorSetContract configuration', async () => { - it('Should the ValidatorSetContract config the StakingContract correctly', async () => { - let _stakingContract = await validatorContract.stakingContract(); - expect(_stakingContract).to.eq(stakingContract.address); - }); - - it('Should the ValidatorSetContract config the Slashing correctly', async () => { - let _slashingContract = await validatorContract.slashIndicatorContract(); - expect(_slashingContract).to.eq(slashContract.address); - }); - }); - - describe('StakingContract configuration', async () => { - it('Should the StakingContract config the ValidatorSetContract correctly', async () => { - let _validatorSetContract = await stakingContract.validatorContract(); - expect(_validatorSetContract).to.eq(validatorContract.address); - }); - }); - - describe('SlashIndicatorContract configuration', async () => { - it('Should the SlashIndicatorContract config the ValidatorSetContract correctly', async () => { - let _validatorSetContract = await slashContract.validatorContract(); - expect(_validatorSetContract).to.eq(validatorContract.address); - }); - }); + governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); + await network.provider.send('hardhat_mine', [ + ethers.utils.hexStripZeros(BigNumber.from(numberOfBlocksInEpoch * numberOfEpochsInPeriod).toHexString()), + ]); }); describe('Slash one validator', async () => { let expectingValidatorSet: Address[] = []; + let period: BigNumberish; + + before(async () => { + const currentBlock = await ethers.provider.getBlockNumber(); + period = await validatorContract.periodOf(currentBlock); + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + }); describe('Slash misdemeanor validator', async () => { it('Should the ValidatorSet contract emit event', async () => { @@ -151,8 +87,10 @@ describe('[Integration] Slash validators', () => { } let tx = slashContract.connect(coinbase).slash(slashee.address); - await expect(tx).to.emit(slashContract, 'ValidatorSlashed').withArgs(slashee.address, SlashType.MISDEMEANOR); - await expect(tx).to.emit(validatorContract, 'ValidatorSlashed').withArgs(slashee.address, 0, 0); + await expect(tx) + .to.emit(slashContract, 'UnavailabilitySlashed') + .withArgs(slashee.address, SlashType.MISDEMEANOR, period); + await expect(tx).to.emit(validatorContract, 'ValidatorPunished').withArgs(slashee.address, 0, 0); }); }); @@ -167,9 +105,11 @@ describe('[Integration] Slash validators', () => { slasheeIdx = 2; slashee = validatorCandidates[slasheeIdx]; slasheeInitStakingAmount = minValidatorBalance.add(slashFelonyAmount.mul(10)); - await stakingContract.connect(slashee).proposeValidator(slashee.address, slashee.address, 2_00, { - value: slasheeInitStakingAmount, - }); + await stakingContract + .connect(slashee) + .proposeValidator(slashee.address, slashee.address, slashee.address, 2_00, { + value: slasheeInitStakingAmount, + }); expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(slasheeInitStakingAmount); @@ -191,20 +131,20 @@ describe('[Integration] Slash validators', () => { slashValidatorTx = await slashContract.connect(coinbase).slash(slashee.address); await expect(slashValidatorTx) - .to.emit(slashContract, 'ValidatorSlashed') - .withArgs(slashee.address, SlashType.FELONY); + .to.emit(slashContract, 'UnavailabilitySlashed') + .withArgs(slashee.address, SlashType.FELONY, period); let blockNumber = await network.provider.send('eth_blockNumber'); await expect(slashValidatorTx) - .to.emit(validatorContract, 'ValidatorSlashed') - .withArgs(slashee.address, BigNumber.from(blockNumber).add(felonyJailDuration), slashFelonyAmount); + .to.emit(validatorContract, 'ValidatorPunished') + .withArgs(slashee.address, BigNumber.from(blockNumber).add(felonyJailBlocks), slashFelonyAmount); }); it('Should the validator is put in jail', async () => { let blockNumber = await network.provider.send('eth_blockNumber'); expect(await validatorContract.getJailUntils(expectingValidatorSet)).eql([ - BigNumber.from(blockNumber).add(felonyJailDuration), + BigNumber.from(blockNumber).add(felonyJailBlocks), ]); }); @@ -275,9 +215,11 @@ describe('[Integration] Slash validators', () => { slashee = validatorCandidates[slasheeIdx]; slasheeInitStakingAmount = minValidatorBalance; - await stakingContract.connect(slashee).proposeValidator(slashee.address, slashee.address, 2_00, { - value: slasheeInitStakingAmount, - }); + await stakingContract + .connect(slashee) + .proposeValidator(slashee.address, slashee.address, slashee.address, 2_00, { + value: slasheeInitStakingAmount, + }); expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(slasheeInitStakingAmount); @@ -298,20 +240,20 @@ describe('[Integration] Slash validators', () => { slashValidatorTx = await slashContract.connect(coinbase).slash(slashee.address); await expect(slashValidatorTx) - .to.emit(slashContract, 'ValidatorSlashed') - .withArgs(slashee.address, SlashType.FELONY); + .to.emit(slashContract, 'UnavailabilitySlashed') + .withArgs(slashee.address, SlashType.FELONY, period); let blockNumber = await network.provider.send('eth_blockNumber'); await expect(slashValidatorTx) - .to.emit(validatorContract, 'ValidatorSlashed') - .withArgs(slashee.address, BigNumber.from(blockNumber).add(felonyJailDuration), slashFelonyAmount); + .to.emit(validatorContract, 'ValidatorPunished') + .withArgs(slashee.address, BigNumber.from(blockNumber).add(felonyJailBlocks), slashFelonyAmount); }); it('Should the validator is put in jail', async () => { let blockNumber = await network.provider.send('eth_blockNumber'); expect(await validatorContract.getJailUntils([slashee.address])).eql([ - BigNumber.from(blockNumber).add(felonyJailDuration), + BigNumber.from(blockNumber).add(felonyJailBlocks), ]); }); diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index de8c1bda3..527ffaca5 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -1,7 +1,8 @@ import { expect } from 'chai'; -import { network, ethers, deployments } from 'hardhat'; +import { network, ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs'; +import { BigNumber, ContractTransaction } from 'ethers'; import { SlashIndicator, @@ -10,104 +11,52 @@ import { Staking__factory, MockRoninValidatorSetExtends__factory, MockRoninValidatorSetExtends, - ProxyAdmin__factory, } from '../../src/types'; -import { - Network, - slashIndicatorConf, - roninValidatorSetConf, - stakingConfig, - initAddress, - stakingVestingConfig, -} from '../../src/config'; -import { BigNumber, ContractTransaction } from 'ethers'; import { mineBatchTxs } from '../helpers/utils'; +import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; let slashContract: SlashIndicator; let stakingContract: Staking; let validatorContract: MockRoninValidatorSetExtends; +let governanceAdmin: GovernanceAdminInterface; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; -let governanceAdmin: SignerWithAddress; +let governor: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; -const felonyJailDuration = 28800 * 2; -const misdemeanorThreshold = 10; -const felonyThreshold = 20; +const felonyThreshold = 10; const slashFelonyAmount = BigNumber.from(1); const slashDoubleSignAmount = 1000; - -const maxValidatorNumber = 3; -const maxPrioritizedValidatorNumber = 0; -const numberOfBlocksInEpoch = 600; -const numberOfEpochsInPeriod = 48; - const minValidatorBalance = BigNumber.from(100); -const maxValidatorCandidate = 10; - const bonusPerBlock = BigNumber.from(1); -const topUpAmount = BigNumber.from(10000); describe('[Integration] Submit Block Reward', () => { const blockRewardAmount = BigNumber.from(2); before(async () => { - [deployer, coinbase, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + [deployer, coinbase, governor, ...validatorCandidates] = await ethers.getSigners(); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + governanceAdmin = new GovernanceAdminInterface(governor); + + const { slashContractAddress, stakingContractAddress, validatorContractAddress } = await initTest( + 'ActionSubmitReward' + )({ + felonyThreshold, + minValidatorBalance, + bonusPerBlock, + slashFelonyAmount, + slashDoubleSignAmount, + governanceAdmin: governanceAdmin.address, + }); - if (network.name == Network.Hardhat) { - initAddress[network.name] = { - governanceAdmin: governanceAdmin.address, - }; - slashIndicatorConf[network.name] = { - misdemeanorThreshold: misdemeanorThreshold, - felonyThreshold: felonyThreshold, - slashFelonyAmount: slashFelonyAmount, - slashDoubleSignAmount: slashDoubleSignAmount, - felonyJailBlocks: felonyJailDuration, - }; - roninValidatorSetConf[network.name] = { - maxValidatorNumber: maxValidatorNumber, - maxValidatorCandidate: maxValidatorCandidate, - maxPrioritizedValidatorNumber: maxPrioritizedValidatorNumber, - numberOfBlocksInEpoch: numberOfBlocksInEpoch, - numberOfEpochsInPeriod: numberOfEpochsInPeriod, - }; - stakingConfig[network.name] = { - minValidatorBalance: minValidatorBalance, - }; - stakingVestingConfig[network.name] = { - bonusPerBlock: bonusPerBlock, - topupAmount: topUpAmount, - }; - } - - await deployments.fixture([ - 'ProxyAdmin', - 'CalculateAddresses', - 'RoninValidatorSetProxy', - 'SlashIndicatorProxy', - 'StakingProxy', - 'StakingVestingProxy', - ]); - - const slashContractDeployment = await deployments.get('SlashIndicatorProxy'); - slashContract = SlashIndicator__factory.connect(slashContractDeployment.address, deployer); - - const stakingContractDeployment = await deployments.get('StakingProxy'); - stakingContract = Staking__factory.connect(stakingContractDeployment.address, deployer); - - const validatorContractDeployment = await deployments.get('RoninValidatorSetProxy'); - validatorContract = MockRoninValidatorSetExtends__factory.connect(validatorContractDeployment.address, deployer); + slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); + stakingContract = Staking__factory.connect(stakingContractAddress, deployer); + validatorContract = MockRoninValidatorSetExtends__factory.connect(validatorContractAddress, deployer); const mockValidatorLogic = await new MockRoninValidatorSetExtends__factory(deployer).deploy(); await mockValidatorLogic.deployed(); - - const proxyAdminDeployment = await deployments.get('ProxyAdmin'); - let proxyAdminContract = ProxyAdmin__factory.connect(proxyAdminDeployment.address, deployer); - - await proxyAdminContract.upgrade(validatorContract.address, mockValidatorLogic.address); + governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); }); describe('Configuration check', async () => { @@ -138,9 +87,11 @@ describe('[Integration] Submit Block Reward', () => { before(async () => { let initStakingAmount = minValidatorBalance.mul(2); validator = validatorCandidates[0]; - await stakingContract.connect(validator).proposeValidator(validator.address, validator.address, 2_00, { - value: initStakingAmount, - }); + await stakingContract + .connect(validator) + .proposeValidator(validator.address, validator.address, validator.address, 2_00, { + value: initStakingAmount, + }); await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); await validatorContract.connect(coinbase).wrapUpEpoch(); @@ -182,9 +133,12 @@ describe('[Integration] Submit Block Reward', () => { before(async () => { let initStakingAmount = minValidatorBalance.mul(2); validator = validatorCandidates[1]; - await stakingContract.connect(validator).proposeValidator(validator.address, validator.address, 2_00, { - value: initStakingAmount, - }); + + await stakingContract + .connect(validator) + .proposeValidator(validator.address, validator.address, validator.address, 2_00, { + value: initStakingAmount, + }); await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index cc88b2c72..ab21cd26e 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -1,6 +1,7 @@ import { expect } from 'chai'; -import { network, ethers, deployments } from 'hardhat'; +import { network, ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { BigNumber, ContractTransaction } from 'ethers'; import { SlashIndicator, @@ -9,107 +10,57 @@ import { Staking__factory, MockRoninValidatorSetExtends__factory, MockRoninValidatorSetExtends, - ProxyAdmin__factory, } from '../../src/types'; -import { - Network, - slashIndicatorConf, - roninValidatorSetConf, - stakingConfig, - initAddress, - stakingVestingConfig, -} from '../../src/config'; -import { BigNumber, ContractTransaction } from 'ethers'; import { expects as StakingExpects } from '../helpers/reward-calculation'; -import { expects as SlashExpects } from '../helpers/slash-indicator'; import { expects as ValidatorSetExpects } from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../helpers/utils'; +import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; let slashContract: SlashIndicator; let stakingContract: Staking; let validatorContract: MockRoninValidatorSetExtends; +let governanceAdmin: GovernanceAdminInterface; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; -let governanceAdmin: SignerWithAddress; +let governor: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; -const felonyJailDuration = 28800 * 2; -const misdemeanorThreshold = 10; -const felonyThreshold = 20; +const felonyThreshold = 10; const slashFelonyAmount = BigNumber.from(1); const slashDoubleSignAmount = 1000; - -const maxValidatorNumber = 3; -const maxPrioritizedValidatorNumber = 0; -const numberOfBlocksInEpoch = 600; -const numberOfEpochsInPeriod = 48; - const minValidatorBalance = BigNumber.from(100); -const maxValidatorCandidate = 10; - -const bonusPerBlock = BigNumber.from(1); -const topUpAmount = BigNumber.from(10000); +const maxValidatorNumber = 3; describe('[Integration] Wrap up epoch', () => { const blockRewardAmount = BigNumber.from(2); before(async () => { - [deployer, coinbase, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + [deployer, coinbase, governor, ...validatorCandidates] = await ethers.getSigners(); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); - - if (network.name == Network.Hardhat) { - initAddress[network.name] = { - governanceAdmin: governanceAdmin.address, - }; - slashIndicatorConf[network.name] = { - misdemeanorThreshold: misdemeanorThreshold, - felonyThreshold: felonyThreshold, - slashFelonyAmount: slashFelonyAmount, - slashDoubleSignAmount: slashDoubleSignAmount, - felonyJailBlocks: felonyJailDuration, - }; - roninValidatorSetConf[network.name] = { - maxValidatorNumber: maxValidatorNumber, - maxValidatorCandidate: maxValidatorCandidate, - maxPrioritizedValidatorNumber: maxPrioritizedValidatorNumber, - numberOfBlocksInEpoch: numberOfBlocksInEpoch, - numberOfEpochsInPeriod: numberOfEpochsInPeriod, - }; - stakingConfig[network.name] = { - minValidatorBalance: minValidatorBalance, - }; - stakingVestingConfig[network.name] = { - bonusPerBlock: bonusPerBlock, - topupAmount: topUpAmount, - }; - } - - await deployments.fixture([ - 'ProxyAdmin', - 'CalculateAddresses', - 'RoninValidatorSetProxy', - 'SlashIndicatorProxy', - 'StakingProxy', - 'StakingVestingProxy', - ]); - - const slashContractDeployment = await deployments.get('SlashIndicatorProxy'); - slashContract = SlashIndicator__factory.connect(slashContractDeployment.address, deployer); - - const stakingContractDeployment = await deployments.get('StakingProxy'); - stakingContract = Staking__factory.connect(stakingContractDeployment.address, deployer); - - const validatorContractDeployment = await deployments.get('RoninValidatorSetProxy'); - validatorContract = MockRoninValidatorSetExtends__factory.connect(validatorContractDeployment.address, deployer); + governanceAdmin = new GovernanceAdminInterface(governor); + + const { slashContractAddress, stakingContractAddress, validatorContractAddress } = await initTest( + 'ActionWrapUpEpoch' + )({ + felonyThreshold, + slashFelonyAmount, + slashDoubleSignAmount, + minValidatorBalance, + maxValidatorNumber, + governanceAdmin: governanceAdmin.address, + }); + slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); + stakingContract = Staking__factory.connect(stakingContractAddress, deployer); + validatorContract = MockRoninValidatorSetExtends__factory.connect(validatorContractAddress, deployer); const mockValidatorLogic = await new MockRoninValidatorSetExtends__factory(deployer).deploy(); await mockValidatorLogic.deployed(); + governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); + }); - const proxyAdminDeployment = await deployments.get('ProxyAdmin'); - let proxyAdminContract = ProxyAdmin__factory.connect(proxyAdminDeployment.address, deployer); - - await proxyAdminContract.upgrade(validatorContract.address, mockValidatorLogic.address); + after(async () => { + await network.provider.send('hardhat_setCoinbase', [ethers.constants.AddressZero]); }); describe('Configuration test', () => { @@ -150,9 +101,15 @@ describe('[Integration] Wrap up epoch', () => { for (let i = 0; i < validators.length; i++) { await stakingContract .connect(validatorCandidates[i]) - .proposeValidator(validatorCandidates[i].address, validatorCandidates[i].address, 2_00, { - value: minValidatorBalance.mul(2).add(i), - }); + .proposeValidator( + validatorCandidates[i].address, + validatorCandidates[i].address, + validatorCandidates[i].address, + 2_00, + { + value: minValidatorBalance.mul(2).add(i), + } + ); } await mineBatchTxs(async () => { @@ -160,17 +117,14 @@ describe('[Integration] Wrap up epoch', () => { await validatorContract.connect(coinbase).wrapUpEpoch(); }); - await network.provider.send('hardhat_setCoinbase', [validators[3].address]); - validatorContract = validatorContract.connect(validators[3]); + coinbase = validators[3]; + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + validatorContract = validatorContract.connect(coinbase); await validatorContract.submitBlockReward({ value: blockRewardAmount, }); }); - after(async () => { - coinbase = validators[3]; - }); - describe('Wrap up epoch: at the end of the epoch', async () => { it('Should validator not be able to wrap up the epoch twice, in the same epoch', async () => { await mineBatchTxs(async () => { @@ -206,27 +160,29 @@ describe('[Integration] Wrap up epoch', () => { }); describe('Wrap up epoch: at the end of the period', async () => { + before(async () => { + await Promise.all(validators.map((v) => slashContract.connect(coinbase).slash(v.address))); + }); + it('Should the ValidatorSet not reset counter, when the period is not ended', async () => { await mineBatchTxs(async () => { await validatorContract.endEpoch(); - wrapUpTx = await validatorContract.wrapUpEpoch(); + wrapUpTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); - await expect(wrapUpTx).not.to.emit(slashContract, 'UnavailabilityIndicatorsReset'); + expect( + await Promise.all(validators.map(async (v) => slashContract.currentUnavailabilityIndicator(v.address))) + ).eql(validators.map((v) => (v.address == coinbase.address ? BigNumber.from(0) : BigNumber.from(1)))); }); it('Should the ValidatorSet reset counter in SlashIndicator contract', async () => { await mineBatchTxs(async () => { await validatorContract.endEpoch(); await validatorContract.endPeriod(); - wrapUpTx = await validatorContract.wrapUpEpoch(); + wrapUpTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); - await SlashExpects.emitUnavailabilityIndicatorsResetEvent( - wrapUpTx, - validators - .slice(1, 4) - .map((_) => _.address) - .reverse() - ); + expect( + await Promise.all(validators.map(async (v) => slashContract.currentUnavailabilityIndicator(v.address))) + ).eql(validators.map(() => BigNumber.from(0))); }); }); }); @@ -241,7 +197,7 @@ describe('[Integration] Wrap up epoch', () => { for (let i = 0; i < validators.length; i++) { await stakingContract .connect(validators[i]) - .proposeValidator(validators[i].address, validators[i].address, 2_00, { + .proposeValidator(validators[i].address, validators[i].address, validators[i].address, 2_00, { value: minValidatorBalance.mul(3).add(i), }); } @@ -251,8 +207,9 @@ describe('[Integration] Wrap up epoch', () => { await validatorContract.connect(coinbase).wrapUpEpoch(); }); - await network.provider.send('hardhat_setCoinbase', [validators[3].address]); - validatorContract = validatorContract.connect(validators[3]); + coinbase = validators[3]; + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + validatorContract = validatorContract.connect(coinbase); await validatorContract.submitBlockReward({ value: blockRewardAmount, }); @@ -266,7 +223,7 @@ describe('[Integration] Wrap up epoch', () => { }); for (let i = 0; i < felonyThreshold; i++) { - await slashContract.connect(validators[3]).slash(validators[1].address); + await slashContract.connect(coinbase).slash(validators[1].address); } }); @@ -278,7 +235,7 @@ describe('[Integration] Wrap up epoch', () => { await ValidatorSetExpects.emitValidatorSetUpdatedEvent( wrapUpTx, - [validators[0], validators[2], validators[3]].map((_) => _.address).reverse() + [validators[0], validators[2], coinbase].map((_) => _.address).reverse() ); }); @@ -288,10 +245,9 @@ describe('[Integration] Wrap up epoch', () => { await validatorContract.endPeriod(); wrapUpTx = await validatorContract.wrapUpEpoch(); }); - await SlashExpects.emitUnavailabilityIndicatorsResetEvent( - wrapUpTx, - [validators[0], validators[2], validators[3]].map((_) => _.address).reverse() - ); + expect( + await Promise.all(validators.map(async (v) => slashContract.currentUnavailabilityIndicator(v.address))) + ).eql(validators.map(() => BigNumber.from(0))); }); }); }); diff --git a/test/integration/Configuration.test.ts b/test/integration/Configuration.test.ts index 0407375d4..6084e06fb 100644 --- a/test/integration/Configuration.test.ts +++ b/test/integration/Configuration.test.ts @@ -1,6 +1,7 @@ import { expect } from 'chai'; -import { network, ethers, deployments } from 'hardhat'; +import { ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { BigNumber } from 'ethers'; import { SlashIndicator, @@ -9,17 +10,15 @@ import { Staking__factory, RoninValidatorSet, RoninValidatorSet__factory, + Maintenance__factory, + Maintenance, + StakingVesting__factory, + StakingVesting, } from '../../src/types'; -import { - Network, - slashIndicatorConf, - roninValidatorSetConf, - stakingConfig, - stakingVestingConfig, - initAddress, -} from '../../src/config'; -import { BigNumber } from 'ethers'; +import { initTest } from '../helpers/fixture'; +let stakingVestingContract: StakingVesting; +let maintenanceContract: Maintenance; let slashContract: SlashIndicator; let stakingContract: Staking; let validatorContract: RoninValidatorSet; @@ -27,14 +26,13 @@ let validatorContract: RoninValidatorSet; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; let governanceAdmin: SignerWithAddress; -let proxyAdmin: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; -const felonyJailDuration = 28800 * 2; -const misdemeanorThreshold = 10; -const felonyThreshold = 20; -const slashFelonyAmount = BigNumber.from(1); -const slashDoubleSignAmount = 1000; +const felonyJailBlocks = 28800 * 2; +const misdemeanorThreshold = 5; +const felonyThreshold = 10; +const slashFelonyAmount = BigNumber.from(10).pow(18).mul(1); +const slashDoubleSignAmount = BigNumber.from(10).pow(18).mul(10); const maxValidatorNumber = 4; const maxPrioritizedValidatorNumber = 0; @@ -45,49 +43,71 @@ const minValidatorBalance = BigNumber.from(100); const maxValidatorCandidate = 10; const bonusPerBlock = BigNumber.from(1); -const topUpAmount = BigNumber.from(10000); +const topupAmount = BigNumber.from(10000); +const minMaintenanceBlockPeriod = 100; +const maxMaintenanceBlockPeriod = 1000; +const minOffset = 200; +const maxSchedules = 2; describe('[Integration] Configuration check', () => { before(async () => { - [coinbase, deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); - - if (network.name == Network.Hardhat) { - initAddress[network.name] = { - governanceAdmin: governanceAdmin.address, - }; - slashIndicatorConf[network.name] = { - misdemeanorThreshold: misdemeanorThreshold, - felonyThreshold: felonyThreshold, - slashFelonyAmount: slashFelonyAmount, - slashDoubleSignAmount: slashDoubleSignAmount, - felonyJailBlocks: felonyJailDuration, - }; - roninValidatorSetConf[network.name] = { - maxValidatorNumber: maxValidatorNumber, - maxValidatorCandidate: maxValidatorNumber, - maxPrioritizedValidatorNumber: maxPrioritizedValidatorNumber, - numberOfBlocksInEpoch: numberOfBlocksInEpoch, - numberOfEpochsInPeriod: numberOfEpochsInPeriod, - }; - stakingConfig[network.name] = { - minValidatorBalance: minValidatorBalance, - }; - stakingVestingConfig[network.name] = { - bonusPerBlock: bonusPerBlock, - topupAmount: topUpAmount, - }; - } - - await deployments.fixture(['CalculateAddresses', 'RoninValidatorSetProxy', 'SlashIndicatorProxy', 'StakingProxy']); - - const slashContractDeployment = await deployments.get('SlashIndicatorProxy'); - slashContract = SlashIndicator__factory.connect(slashContractDeployment.address, deployer); - - const stakingContractDeployment = await deployments.get('StakingProxy'); - stakingContract = Staking__factory.connect(stakingContractDeployment.address, deployer); - - const validatorContractDeployment = await deployments.get('RoninValidatorSetProxy'); - validatorContract = RoninValidatorSet__factory.connect(validatorContractDeployment.address, deployer); + [coinbase, deployer, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + const { + maintenanceContractAddress, + slashContractAddress, + stakingContractAddress, + validatorContractAddress, + stakingVestingContractAddress, + } = await initTest('Configuration')({ + felonyJailBlocks, + misdemeanorThreshold, + felonyThreshold, + slashFelonyAmount, + slashDoubleSignAmount, + maxValidatorNumber, + maxPrioritizedValidatorNumber, + numberOfBlocksInEpoch, + numberOfEpochsInPeriod, + minValidatorBalance, + maxValidatorCandidate, + bonusPerBlock, + topupAmount, + minMaintenanceBlockPeriod, + maxMaintenanceBlockPeriod, + minOffset, + maxSchedules, + governanceAdmin: governanceAdmin.address, + }); + + stakingVestingContract = StakingVesting__factory.connect(stakingVestingContractAddress, deployer); + maintenanceContract = Maintenance__factory.connect(maintenanceContractAddress, deployer); + slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); + stakingContract = Staking__factory.connect(stakingContractAddress, deployer); + validatorContract = RoninValidatorSet__factory.connect(validatorContractAddress, deployer); + }); + + describe('Maintenance configuration', () => { + it('Should the MaintenanceContract config the validator contract correctly', async () => { + expect(await maintenanceContract.validatorContract()).eq(validatorContract.address); + }); + + it('Should the MaintenanceContract set the maintenance config correctly', async () => { + expect(await maintenanceContract.minMaintenanceBlockPeriod()).eq(minMaintenanceBlockPeriod); + expect(await maintenanceContract.maxMaintenanceBlockPeriod()).eq(maxMaintenanceBlockPeriod); + expect(await maintenanceContract.minOffset()).eq(minOffset); + expect(await maintenanceContract.maxSchedules()).eq(maxSchedules); + }); + }); + + describe('StakingVesting configuration', () => { + it('Should the StakingVestingContract config the validator contract correctly', async () => { + expect(await stakingVestingContract.validatorContract()).eq(validatorContract.address); + }); + + it('Should the StakingVestingContract config the block bonus correctly', async () => { + expect(await stakingVestingContract.blockBonus(0)).eq(bonusPerBlock); + expect(await stakingVestingContract.blockBonus(Math.floor(Math.random() * 1_000_000))).eq(bonusPerBlock); + }); }); describe('ValidatorSetContract configuration', async () => { @@ -108,7 +128,7 @@ describe('[Integration] Configuration check', () => { it('Should config the maxValidatorCandidate correctly', async () => { let _maxValidatorCandidate = await validatorContract.maxValidatorCandidate(); - expect(_maxValidatorCandidate).to.eq(maxValidatorNumber); + expect(_maxValidatorCandidate).to.eq(maxValidatorCandidate); }); it('Should config the numberOfBlocksInEpoch correctly', async () => { @@ -162,7 +182,7 @@ describe('[Integration] Configuration check', () => { it('Should config the felonyJailDuration correctly', async () => { let _felonyJailDuration = await slashContract.felonyJailDuration(); - expect(_felonyJailDuration).to.eq(felonyJailDuration); + expect(_felonyJailDuration).to.eq(felonyJailBlocks); }); }); }); diff --git a/test/maintainance/Maintenance.test.ts b/test/maintainance/Maintenance.test.ts new file mode 100644 index 000000000..44a721151 --- /dev/null +++ b/test/maintainance/Maintenance.test.ts @@ -0,0 +1,304 @@ +import { expect } from 'chai'; +import { ethers, network } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { BigNumber, BigNumberish } from 'ethers'; + +import { + RoninValidatorSet, + RoninValidatorSet__factory, + Maintenance, + Maintenance__factory, + SlashIndicator, + SlashIndicator__factory, + Staking, + Staking__factory, +} from '../../src/types'; +import { initTest } from '../helpers/fixture'; + +let coinbase: SignerWithAddress; +let deployer: SignerWithAddress; +let governanceAdmin: SignerWithAddress; +let validatorCandidates: SignerWithAddress[]; + +let maintenanceContract: Maintenance; +let slashContract: SlashIndicator; +let stakingContract: Staking; +let validatorContract: RoninValidatorSet; + +const misdemeanorThreshold = 50; +const felonyThreshold = 150; +const maxValidatorNumber = 4; +const numberOfBlocksInEpoch = 600; +const numberOfEpochsInPeriod = 48; +const minValidatorBalance = BigNumber.from(100); +const minMaintenanceBlockPeriod = 100; +const maxMaintenanceBlockPeriod = 1000; +const minOffset = 200; + +let startedAtBlock: BigNumberish = 0; +let endedAtBlock: BigNumberish = 0; +let currentBlock: number; + +const calculateStartOfEpoch = (block: number) => + BigNumber.from( + Math.floor((block + minOffset + numberOfBlocksInEpoch - 1) / numberOfBlocksInEpoch) * numberOfBlocksInEpoch + ); +const diffToEndEpoch = (block: BigNumberish) => + BigNumber.from(numberOfBlocksInEpoch).sub(BigNumber.from(block).mod(numberOfBlocksInEpoch)).sub(1); +const calculateEndOfEpoch = (block: BigNumberish) => BigNumber.from(block).add(diffToEndEpoch(block)); +const mineToBeforeEndOfEpoch = async () => { + let number = diffToEndEpoch(await ethers.provider.getBlockNumber()).sub(1); + if (number.lt(0)) { + number = number.add(numberOfBlocksInEpoch); + } + return network.provider.send('hardhat_mine', [ethers.utils.hexStripZeros(number.toHexString())]); +}; + +describe('Maintenance test', () => { + before(async () => { + [deployer, coinbase, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + const { maintenanceContractAddress, slashContractAddress, stakingContractAddress, validatorContractAddress } = + await initTest('Maintenance')({ + governanceAdmin: governanceAdmin.address, + misdemeanorThreshold, + felonyThreshold, + }); + maintenanceContract = Maintenance__factory.connect(maintenanceContractAddress, deployer); + slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); + stakingContract = Staking__factory.connect(stakingContractAddress, deployer); + validatorContract = RoninValidatorSet__factory.connect(validatorContractAddress, deployer); + + validatorCandidates = validatorCandidates.slice(0, maxValidatorNumber); + for (let i = 0; i < maxValidatorNumber; i++) { + await stakingContract + .connect(validatorCandidates[i]) + .proposeValidator( + validatorCandidates[i].address, + validatorCandidates[i].address, + validatorCandidates[i].address, + 1, + { value: minValidatorBalance.add(maxValidatorNumber).sub(i) } + ); + } + + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + await network.provider.send('hardhat_mine', [ + ethers.utils.hexStripZeros(BigNumber.from(numberOfBlocksInEpoch * numberOfEpochsInPeriod).toHexString()), + ]); + await mineToBeforeEndOfEpoch(); + + await validatorContract.connect(coinbase).wrapUpEpoch(); + expect(await validatorContract.getValidators()).eql(validatorCandidates.map((_) => _.address)); + }); + + after(async () => { + await network.provider.send('hardhat_setCoinbase', [ethers.constants.AddressZero]); + }); + + describe('Configuration test', () => { + before(async () => { + currentBlock = (await ethers.provider.getBlockNumber()) + 1; + }); + + it('Should be not able to schedule maintenance with invalid offset', async () => { + startedAtBlock = 0; + endedAtBlock = 100; + expect(startedAtBlock - currentBlock).lt(minOffset); + await expect( + maintenanceContract + .connect(validatorCandidates[0]) + .schedule(validatorCandidates[0].address, startedAtBlock, endedAtBlock) + ).revertedWith('Maintenance: invalid offset size'); + + startedAtBlock = currentBlock; + endedAtBlock = currentBlock + 1000; + expect(startedAtBlock - currentBlock).lt(minOffset); + await expect( + maintenanceContract + .connect(validatorCandidates[0]) + .schedule(validatorCandidates[0].address, startedAtBlock, endedAtBlock) + ).revertedWith('Maintenance: invalid offset size'); + }); + + it('Should be not able to schedule maintenance in case of: start block >= end block', async () => { + startedAtBlock = currentBlock + minOffset; + endedAtBlock = currentBlock; + expect(endedAtBlock).lte(startedAtBlock); + await expect( + maintenanceContract + .connect(validatorCandidates[0]) + .schedule(validatorCandidates[0].address, startedAtBlock, endedAtBlock) + ).revertedWith('Maintenance: start block must be less than end block'); + + endedAtBlock = startedAtBlock; + expect(endedAtBlock).lte(startedAtBlock); + await expect( + maintenanceContract + .connect(validatorCandidates[0]) + .schedule(validatorCandidates[0].address, startedAtBlock, endedAtBlock) + ).revertedWith('Maintenance: start block must be less than end block'); + }); + + it('Should be not able to schedule maintenance when the maintenance period is too small or large', async () => { + endedAtBlock = BigNumber.from(startedAtBlock).add(1); + expect(endedAtBlock.sub(startedAtBlock)).lt(minMaintenanceBlockPeriod); + await expect( + maintenanceContract + .connect(validatorCandidates[0]) + .schedule(validatorCandidates[0].address, startedAtBlock, endedAtBlock) + ).revertedWith('Maintenance: invalid maintenance block period'); + + endedAtBlock = BigNumber.from(startedAtBlock).add(maxMaintenanceBlockPeriod).add(1); + expect(endedAtBlock.sub(startedAtBlock)).gt(maxMaintenanceBlockPeriod); + await expect( + maintenanceContract + .connect(validatorCandidates[0]) + .schedule(validatorCandidates[0].address, startedAtBlock, endedAtBlock) + ).revertedWith('Maintenance: invalid maintenance block period'); + }); + + it('Should be not able to schedule maintenance when the start block is not at the start of an epoch', async () => { + startedAtBlock = calculateStartOfEpoch(currentBlock).add(1); + endedAtBlock = calculateEndOfEpoch(startedAtBlock.add(minMaintenanceBlockPeriod)); + + expect(startedAtBlock.mod(numberOfBlocksInEpoch)).not.eq(0); + expect(endedAtBlock.mod(numberOfBlocksInEpoch)).eq(numberOfBlocksInEpoch - 1); + await expect( + maintenanceContract + .connect(validatorCandidates[0]) + .schedule(validatorCandidates[0].address, startedAtBlock, endedAtBlock) + ).revertedWith('Maintenance: start block is not at the start of an epoch'); + }); + + it('Should be not able to schedule maintenance when the end block is not at the end of an epoch', async () => { + currentBlock = (await ethers.provider.getBlockNumber()) + 1; + startedAtBlock = calculateStartOfEpoch(currentBlock); + endedAtBlock = calculateEndOfEpoch(startedAtBlock.add(minMaintenanceBlockPeriod)).add(1); + + expect(startedAtBlock.mod(numberOfBlocksInEpoch)).eq(0); + expect(endedAtBlock.mod(numberOfBlocksInEpoch)).not.eq(numberOfBlocksInEpoch - 1); + await expect( + maintenanceContract + .connect(validatorCandidates[0]) + .schedule(validatorCandidates[0].address, startedAtBlock, endedAtBlock) + ).revertedWith('Maintenance: end block is not at the end of an epoch'); + }); + }); + + describe('Schedule test', () => { + it('Should not be able to schedule maintenance using unauthorized account', async () => { + await expect(maintenanceContract.connect(deployer).schedule(validatorCandidates[0].address, 0, 100)).revertedWith( + 'Maintenance: method caller must be a candidate admin' + ); + }); + + it('Should not be able to schedule maintenance for non-validator address', async () => { + await expect(maintenanceContract.connect(validatorCandidates[0]).schedule(deployer.address, 0, 100)).revertedWith( + 'Maintenance: consensus address must be a validator' + ); + }); + + it('Should be able to schedule maintenance using validator admin account', async () => { + currentBlock = (await ethers.provider.getBlockNumber()) + 1; + startedAtBlock = calculateStartOfEpoch(currentBlock).add(numberOfBlocksInEpoch); + endedAtBlock = calculateEndOfEpoch(BigNumber.from(startedAtBlock).add(minMaintenanceBlockPeriod)); + + const tx = await maintenanceContract + .connect(validatorCandidates[0]) + .schedule(validatorCandidates[0].address, startedAtBlock, endedAtBlock); + await expect(tx) + .emit(maintenanceContract, 'MaintenanceScheduled') + .withArgs(validatorCandidates[0].address, [startedAtBlock, endedAtBlock]); + expect(await maintenanceContract.scheduled(validatorCandidates[0].address)).true; + }); + + it('Should not be able to schedule maintenance again', async () => { + await expect( + maintenanceContract + .connect(validatorCandidates[0]) + .schedule(validatorCandidates[0].address, startedAtBlock, endedAtBlock) + ).revertedWith('Maintenance: already scheduled'); + }); + + it('Should be able to schedule maintenance using another validator admin account', async () => { + await maintenanceContract + .connect(validatorCandidates[1]) + .schedule(validatorCandidates[1].address, startedAtBlock, endedAtBlock); + }); + + it('Should not be able to schedule maintenance once there are many schedules', async () => { + await expect( + maintenanceContract + .connect(validatorCandidates[3]) + .schedule(validatorCandidates[3].address, startedAtBlock, endedAtBlock) + ).revertedWith('Maintenance: exceeds total of schedules'); + }); + + it('Should the validator still appear in the validator list since it is not maintenance time yet', async () => { + await mineToBeforeEndOfEpoch(); + await validatorContract.connect(coinbase).wrapUpEpoch(); + expect(await validatorContract.getValidators()).eql(validatorCandidates.map((_) => _.address)); + }); + + it('Should the validator not appear in the validator list since the maintenance is started', async () => { + await mineToBeforeEndOfEpoch(); + await validatorContract.connect(coinbase).wrapUpEpoch(); + expect(await validatorContract.getValidators()).eql(validatorCandidates.slice(2).map((_) => _.address)); + }); + + it('[Slash Integration] Should not be able to slash the validator in maintenance time', async () => { + await slashContract.connect(coinbase).slash(validatorCandidates[0].address); + expect(await slashContract.currentUnavailabilityIndicator(validatorCandidates[0].address)).eq(0); + await slashContract.connect(coinbase).slash(validatorCandidates[1].address); + expect(await slashContract.currentUnavailabilityIndicator(validatorCandidates[1].address)).eq(0); + }); + + it('[Slash Integration] Should the unavailability thresholds of the validator is rescaled', async () => { + currentBlock = await ethers.provider.getBlockNumber(); + const thresholds = await slashContract.unavailabilityThresholdsOf(validatorCandidates[0].address, currentBlock); + + const blockLength = BigNumber.from(numberOfBlocksInEpoch * numberOfEpochsInPeriod); + const diff = blockLength.sub(BigNumber.from(endedAtBlock).sub(startedAtBlock).add(1)); + + expect(thresholds).eql([ + diff.mul(misdemeanorThreshold).div(blockLength), + diff.mul(felonyThreshold).div(blockLength), + ]); + }); + + it('Should the validator appear in the validator list since the maintenance time is ended', async () => { + await mineToBeforeEndOfEpoch(); + await validatorContract.connect(coinbase).wrapUpEpoch(); + expect(await validatorContract.getValidators()).eql(validatorCandidates.map((_) => _.address)); + }); + + it('Should not be able to schedule maintenance twice in a period', async () => { + currentBlock = (await ethers.provider.getBlockNumber()) + 1; + startedAtBlock = calculateStartOfEpoch(currentBlock); + endedAtBlock = calculateEndOfEpoch(startedAtBlock); + await expect( + maintenanceContract + .connect(validatorCandidates[0]) + .schedule(validatorCandidates[0].address, startedAtBlock, endedAtBlock) + ).revertedWith('Maintenance: schedule twice in a period is not allowed'); + }); + + it('[Slash Integration] Should the unavailability thresholds reset in the next period', async () => { + await network.provider.send('hardhat_mine', [ + ethers.utils.hexStripZeros(BigNumber.from(numberOfBlocksInEpoch * numberOfEpochsInPeriod).toHexString()), + ]); + currentBlock = await ethers.provider.getBlockNumber(); + const thresholds = await slashContract.unavailabilityThresholdsOf(validatorCandidates[0].address, currentBlock); + expect(thresholds.map((v) => v.toNumber())).eql([misdemeanorThreshold, felonyThreshold]); + }); + + it('Should be able to schedule in the next period', async () => { + currentBlock = (await ethers.provider.getBlockNumber()) + 1; + startedAtBlock = calculateStartOfEpoch(currentBlock); + endedAtBlock = calculateEndOfEpoch(startedAtBlock); + await maintenanceContract + .connect(validatorCandidates[0]) + .schedule(validatorCandidates[0].address, startedAtBlock, endedAtBlock); + }); + }); +}); diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index 689dd6356..be203a7b1 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -3,159 +3,147 @@ import { expect } from 'chai'; import { ethers, network } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { - SlashIndicator, - SlashIndicator__factory, - MockValidatorSetForSlash, - MockValidatorSetForSlash__factory, - TransparentUpgradeableProxyV2__factory, -} from '../../src/types'; -import { Address } from 'hardhat-deploy/dist/types'; +import { SlashIndicator, SlashIndicator__factory, MockValidatorSet__factory, MockValidatorSet } from '../../src/types'; import { SlashType } from '../../src/script/slash-indicator'; -import { Network, slashIndicatorConf } from '../../src/config'; -import { expects as SlashExpects } from '../helpers/slash-indicator'; +import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; let slashContract: SlashIndicator; let deployer: SignerWithAddress; -let proxyAdmin: SignerWithAddress; -let mockValidatorsContract: MockValidatorSetForSlash; +let governanceAdmin: SignerWithAddress; +let mockValidatorsContract: MockValidatorSet; let vagabond: SignerWithAddress; let coinbases: SignerWithAddress[]; -let defaultCoinbase: Address; let localIndicators: number[]; let felonyThreshold: number; let misdemeanorThreshold: number; -const resetCoinbase = async () => { - await network.provider.send('hardhat_setCoinbase', [defaultCoinbase]); -}; - -const increaseLocalCounterForValidatorAt = async (_index: number, _increase?: number) => { - _increase = _increase ?? 1; - localIndicators[_index] = (localIndicators[_index] + _increase) % felonyThreshold; -}; +const maxValidatorCandidate = 10; -const setLocalCounterForValidatorAt = async (_index: number, _value: number) => { - localIndicators[_index] = _value % felonyThreshold; +const increaseLocalCounterForValidatorAt = (idx: number, value?: number) => { + value = value ?? 1; + localIndicators[idx] += value; }; -const resetLocalCounterForValidatorAt = async (_index: number) => { - localIndicators[_index] = 0; +const setLocalCounterForValidatorAt = (idx: number, value: number) => { + localIndicators[idx] = value; }; -const validateIndicatorAt = async (_index: number) => { - expect(localIndicators[_index]).to.eq(await slashContract.getSlashIndicator(coinbases[_index].address)); +const resetLocalCounterForValidatorAt = (idx: number) => { + localIndicators[idx] = 0; }; -const doSlash = async (slasher: SignerWithAddress, slashee: SignerWithAddress) => { - return slashContract.connect(slasher).slash(slashee.address); +const validateIndicatorAt = async (idx: number) => { + expect(localIndicators[idx]).to.eq(await slashContract.currentUnavailabilityIndicator(coinbases[idx].address)); }; describe('Slash indicator test', () => { before(async () => { - [deployer, proxyAdmin, vagabond, ...coinbases] = await ethers.getSigners(); + [deployer, governanceAdmin, vagabond, ...coinbases] = await ethers.getSigners(); localIndicators = Array(coinbases.length).fill(0); - defaultCoinbase = await network.provider.send('eth_coinbase'); - - if (network.name == Network.Hardhat) { - slashIndicatorConf[network.name] = { - misdemeanorThreshold: 10, - felonyThreshold: 20, // set low threshold to get rid of 40000ms of test timeout - slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), // 10 RON - slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), // 10 RON - felonyJailBlocks: 28800 * 2, // jails for 2 days - }; - } - - mockValidatorsContract = await new MockValidatorSetForSlash__factory(deployer).deploy(); - const logicContract = await new SlashIndicator__factory(deployer).deploy(); - const proxyContract = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( - logicContract.address, - proxyAdmin.address, - logicContract.interface.encodeFunctionData('initialize', [ - mockValidatorsContract.address, - slashIndicatorConf[network.name]!.misdemeanorThreshold, - slashIndicatorConf[network.name]!.felonyThreshold, - slashIndicatorConf[network.name]!.slashFelonyAmount, - slashIndicatorConf[network.name]!.slashDoubleSignAmount, - slashIndicatorConf[network.name]!.felonyJailBlocks, - ]) + + const { slashContractAddress, stakingContractAddress, stakingVestingContractAddress } = await initTest( + 'SlashIndicator' + )({ + misdemeanorThreshold: 5, + felonyThreshold: 10, + slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), // 1 RON + slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), // 10 RON + felonyJailBlocks: 28800 * 2, + maxValidatorCandidate, + governanceAdmin: governanceAdmin.address, + }); + + slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); + + // Sets the new validator contract instead of upgrading because the storage is mismatched + mockValidatorsContract = await new MockValidatorSet__factory(deployer).deploy( + stakingContractAddress, + slashContractAddress, + stakingVestingContractAddress, + maxValidatorCandidate, + 600, + 48 + ); + await mockValidatorsContract.deployed(); + + await new GovernanceAdminInterface(governanceAdmin).functionDelegateCall( + slashContract.address, + slashContract.interface.encodeFunctionData('setValidatorContract', [mockValidatorsContract.address]) ); - slashContract = SlashIndicator__factory.connect(proxyContract.address, deployer); - await mockValidatorsContract.connect(deployer).setSlashingContract(slashContract.address); - [misdemeanorThreshold, felonyThreshold] = (await slashContract.getSlashThresholds()).map((_) => _.toNumber()); + [misdemeanorThreshold, felonyThreshold] = (await slashContract.getUnavailabilityThresholds()).map((_) => + _.toNumber() + ); + }); + + after(async () => { + await network.provider.send('hardhat_setCoinbase', [ethers.constants.AddressZero]); }); describe('Single flow test', async () => { describe('Unauthorized test', async () => { it('Should non-coinbase cannot call slash', async () => { await expect(slashContract.connect(vagabond).slash(coinbases[0].address)).to.revertedWith( - 'SlashIndicator: method caller is not the coinbase' - ); - }); - - it('Should non-validatorContract cannot call reset counter', async () => { - await expect(slashContract.connect(vagabond).resetCounters([coinbases[0].address])).to.revertedWith( - 'HasValidatorContract: method caller must be validator contract' + 'SlashIndicator: method caller must be coinbase' ); }); }); describe('Slash method: recording', async () => { it('Should slash a validator successfully', async () => { - let slasherIdx = 0; - let slasheeIdx = 1; + const slasherIdx = 0; + const slasheeIdx = 1; + await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); - let tx = await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); - expect(tx).to.not.emit(slashContract, 'ValidatorSlashed'); + let tx = await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); + expect(tx).to.not.emit(slashContract, 'UnavailabilitySlashed'); setLocalCounterForValidatorAt(slasheeIdx, 1); - validateIndicatorAt(slasheeIdx); + await validateIndicatorAt(slasheeIdx); }); it('Should validator not be able to slash themselves', async () => { - let slasherIdx = 0; - let tx = await doSlash(coinbases[slasherIdx], coinbases[slasherIdx]); - expect(tx).to.not.emit(slashContract, 'ValidatorSlashed'); + const slasherIdx = 0; + let tx = await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasherIdx].address); + expect(tx).to.not.emit(slashContract, 'UnavailabilitySlashed'); - await resetLocalCounterForValidatorAt(slasherIdx); + resetLocalCounterForValidatorAt(slasherIdx); await validateIndicatorAt(slasherIdx); }); it('Should not able to slash twice in one block', async () => { - let slasherIdx = 0; - let slasheeIdx = 2; + const slasherIdx = 0; + const slasheeIdx = 2; await network.provider.send('evm_setAutomine', [false]); - await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); - let tx = doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); + await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); + let tx = slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); await expect(tx).to.be.revertedWith( 'SlashIndicator: cannot slash a validator twice or slash more than one validator in one block' ); await network.provider.send('evm_mine'); await network.provider.send('evm_setAutomine', [true]); - await increaseLocalCounterForValidatorAt(slasheeIdx); + increaseLocalCounterForValidatorAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); }); it('Should not able to slash more than one validator in one block', async () => { - let slasherIdx = 0; - let slasheeIdx1 = 1; - let slasheeIdx2 = 2; + const slasherIdx = 0; + const slasheeIdx1 = 1; + const slasheeIdx2 = 2; await network.provider.send('evm_setAutomine', [false]); - await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx1]); - let tx = doSlash(coinbases[slasherIdx], coinbases[slasheeIdx2]); + await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx1].address); + let tx = slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx2].address); await expect(tx).to.be.revertedWith( 'SlashIndicator: cannot slash a validator twice or slash more than one validator in one block' ); await network.provider.send('evm_mine'); await network.provider.send('evm_setAutomine', [true]); - await increaseLocalCounterForValidatorAt(slasheeIdx1); + increaseLocalCounterForValidatorAt(slasheeIdx1); await validateIndicatorAt(slasheeIdx1); - await setLocalCounterForValidatorAt(slasheeIdx2, 1); + setLocalCounterForValidatorAt(slasheeIdx2, 1); await validateIndicatorAt(slasheeIdx1); }); }); @@ -163,117 +151,110 @@ describe('Slash indicator test', () => { describe('Slash method: recording and call to validator set', async () => { it('Should sync with validator set for misdemeanor (slash tier-1)', async () => { let tx; - let slasherIdx = 1; - let slasheeIdx = 3; + const slasherIdx = 1; + const slasheeIdx = 3; await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); for (let i = 0; i < misdemeanorThreshold; i++) { - tx = await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); + tx = await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); } - expect(tx).to.emit(slashContract, 'ValidatorSlashed').withArgs(coinbases[1].address, SlashType.MISDEMEANOR); - await setLocalCounterForValidatorAt(slasheeIdx, misdemeanorThreshold); + expect(tx) + .to.emit(slashContract, 'UnavailabilitySlashed') + .withArgs(coinbases[1].address, SlashType.MISDEMEANOR); + setLocalCounterForValidatorAt(slasheeIdx, misdemeanorThreshold); await validateIndicatorAt(slasheeIdx); }); it('Should not sync with validator set when the indicator counter is in between misdemeanor (tier-1) and felony (tier-2) thresholds ', async () => { let tx; - let slasherIdx = 1; - let slasheeIdx = 3; + const slasherIdx = 1; + const slasheeIdx = 3; await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); - tx = await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); - await increaseLocalCounterForValidatorAt(slasheeIdx); + tx = await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); + increaseLocalCounterForValidatorAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); - expect(tx).not.to.emit(slashContract, 'ValidatorSlashed'); + expect(tx).not.to.emit(slashContract, 'UnavailabilitySlashed'); }); it('Should sync with validator set for felony (slash tier-2)', async () => { let tx; - let slasherIdx = 0; - let slasheeIdx = 4; + const slasherIdx = 0; + const slasheeIdx = 4; await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); for (let i = 0; i < felonyThreshold; i++) { - tx = await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); + tx = await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); if (i == misdemeanorThreshold - 1) { - expect(tx).to.emit(slashContract, 'ValidatorSlashed').withArgs(coinbases[1].address, SlashType.MISDEMEANOR); + expect(tx) + .to.emit(slashContract, 'UnavailabilitySlashed') + .withArgs(coinbases[1].address, SlashType.MISDEMEANOR); } } - expect(tx).to.emit(slashContract, 'ValidatorSlashed').withArgs(coinbases[1].address, SlashType.FELONY); - await setLocalCounterForValidatorAt(slasheeIdx, felonyThreshold); + expect(tx).to.emit(slashContract, 'UnavailabilitySlashed').withArgs(coinbases[1].address, SlashType.FELONY); + setLocalCounterForValidatorAt(slasheeIdx, felonyThreshold); await validateIndicatorAt(slasheeIdx); }); it('Should not sync with validator set when the indicator counter exceeds felony threshold (tier-2) ', async () => { let tx; - let slasherIdx = 1; - let slasheeIdx = 4; + const slasherIdx = 1; + const slasheeIdx = 4; await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); - tx = await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); - await increaseLocalCounterForValidatorAt(slasheeIdx); + tx = await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); + increaseLocalCounterForValidatorAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); - expect(tx).not.to.emit(slashContract, 'ValidatorSlashed'); + expect(tx).not.to.emit(slashContract, 'UnavailabilitySlashed'); }); }); describe('Resetting counter', async () => { - it('Should validator set contract reset counter for one validator', async () => { - let tx; - let slasherIdx = 0; - let slasheeIdx = 5; + it('Should the counter reset for one validator when the period ended', async () => { + const slasherIdx = 0; + const slasheeIdx = 5; let numberOfSlashing = felonyThreshold - 1; await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); for (let i = 0; i < numberOfSlashing; i++) { - await doSlash(coinbases[slasherIdx], coinbases[slasheeIdx]); + await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); } - await setLocalCounterForValidatorAt(slasheeIdx, numberOfSlashing); - await validateIndicatorAt(slasheeIdx); - await resetCoinbase(); + setLocalCounterForValidatorAt(slasheeIdx, numberOfSlashing); + await validateIndicatorAt(slasheeIdx); - tx = await mockValidatorsContract.resetCounters([coinbases[slasheeIdx].address]); - await SlashExpects.emitUnavailabilityIndicatorsResetEvent(tx, [coinbases[slasheeIdx].address]); + await mockValidatorsContract.endPeriod(); - await resetLocalCounterForValidatorAt(slasheeIdx); + resetLocalCounterForValidatorAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); }); - it('Should validator set contract reset counter for multiple validators', async () => { - let tx; - let slasherIdx = 0; - let slasheeIdxs = [6, 7, 8, 9, 10]; + it('Should the counter reset for multiple validators when the period ended', async () => { + const slasherIdx = 0; + const slasheeIdxs = [6, 7, 8, 9, 10]; let numberOfSlashing = felonyThreshold - 1; await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); for (let i = 0; i < numberOfSlashing; i++) { for (let j = 0; j < slasheeIdxs.length; j++) { - await doSlash(coinbases[slasherIdx], coinbases[slasheeIdxs[j]]); + await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdxs[j]].address); } } for (let j = 0; j < slasheeIdxs.length; j++) { - await setLocalCounterForValidatorAt(slasheeIdxs[j], numberOfSlashing); + setLocalCounterForValidatorAt(slasheeIdxs[j], numberOfSlashing); await validateIndicatorAt(slasheeIdxs[j]); } - await resetCoinbase(); - - tx = await mockValidatorsContract.resetCounters(slasheeIdxs.map((_) => coinbases[_].address)); - - await SlashExpects.emitUnavailabilityIndicatorsResetEvent( - tx, - slasheeIdxs.map((_) => coinbases[_].address) - ); + await mockValidatorsContract.endPeriod(); for (let j = 0; j < slasheeIdxs.length; j++) { - await resetLocalCounterForValidatorAt(slasheeIdxs[j]); + resetLocalCounterForValidatorAt(slasheeIdxs[j]); await validateIndicatorAt(slasheeIdxs[j]); } }); diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index 368456aee..f79c2cb1f 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -122,7 +122,7 @@ describe('Staking test', () => { describe('Validator candidate test', () => { it('Should not be able to propose validator with insufficient amount', async () => { - await expect(stakingContract.proposeValidator(userA.address, userA.address, 1)).revertedWith( + await expect(stakingContract.proposeValidator(userA.address, userA.address, userA.address, 1)).revertedWith( 'StakingManager: insufficient amount' ); }); @@ -130,12 +130,15 @@ describe('Staking test', () => { it('Should be able to propose validator with sufficient amount', async () => { for (let i = 1; i < validatorCandidates.length; i++) { const candidate = validatorCandidates[i]; - const tx = await stakingContract.connect(candidate).proposeValidator( - candidate.address, - candidate.address, - 1, // 0.01% - { value: minValidatorBalance } - ); + const tx = await stakingContract + .connect(candidate) + .proposeValidator( + candidate.address, + candidate.address, + candidate.address, + 1, + /* 0.01% */ { value: minValidatorBalance } + ); await expect(tx).emit(stakingContract, 'ValidatorPoolAdded').withArgs(candidate.address, candidate.address); } @@ -146,7 +149,9 @@ describe('Staking test', () => { it('Should not be able to propose validator again', async () => { await expect( - stakingContract.connect(poolAddr).proposeValidator(poolAddr.address, poolAddr.address, 0, { value: 10 }) + stakingContract + .connect(poolAddr) + .proposeValidator(poolAddr.address, poolAddr.address, poolAddr.address, 0, { value: 10 }) ).revertedWith('CandidateManager: query for already existent candidate'); }); @@ -222,7 +227,9 @@ describe('Staking test', () => { await TransparentUpgradeableProxyV2__factory.connect(stakingContract.address, proxyAdmin).functionDelegateCall( stakingContract.interface.encodeFunctionData('setMinValidatorBalance', [0]) ); - await stakingContract.connect(poolAddr).proposeValidator(poolAddr.address, poolAddr.address, 0, { value: 0 }); + await stakingContract + .connect(poolAddr) + .proposeValidator(poolAddr.address, poolAddr.address, poolAddr.address, 0, { value: 0 }); await network.provider.send('evm_setAutomine', [false]); }); diff --git a/test/validator/RoninValidatorSet-ArrangeValidators.test.ts b/test/validator/RoninValidatorSet-ArrangeValidators.test.ts index 66a1c3ce0..b66eaaf83 100644 --- a/test/validator/RoninValidatorSet-ArrangeValidators.test.ts +++ b/test/validator/RoninValidatorSet-ArrangeValidators.test.ts @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { BigNumber } from 'ethers'; import { ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import * as RoninValidatorSet from '../helpers/ronin-validator-set'; +import { Address } from 'hardhat-deploy/dist/types'; +import * as RoninValidatorSet from '../helpers/ronin-validator-set'; import { Staking, MockRoninValidatorSetExtends, @@ -13,8 +14,8 @@ import { MockSlashIndicator, MockSlashIndicator__factory, StakingVesting__factory, + Maintenance__factory, } from '../../src/types'; -import { Address } from 'hardhat-deploy/dist/types'; let validatorContract: MockRoninValidatorSetExtends; let stakingContract: Staking; @@ -26,7 +27,7 @@ let proxyAdmin: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; const slashFelonyAmount = 100; -const slashDoubleSignAmount = 1000; +const slashDoubleSignAmount = BigNumber.from(10).pow(18).mul(10); const maxValidatorNumber = 7; const maxPrioritizedValidatorNumber = 4; @@ -74,6 +75,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { before(async () => { [deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + const scheduleMaintenance = await new Maintenance__factory(deployer).deploy(); const nonce = await deployer.getTransactionCount(); const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 6 }); @@ -115,6 +117,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { slashIndicator.address, stakingContractAddr, stakingVesting.address, + scheduleMaintenance.address, maxValidatorNumber, maxValidatorCandidate, maxPrioritizedValidatorNumber, @@ -468,7 +471,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { it('Shuffled: Actual(prioritized) == MaxNum(prioritized); Actual(regular) == MaxNum(regular)', async () => { // prettier-ignore - let indexes = [ 0, 1, 2, 3, 4, 5, 6]; + let indexes = [0, 1, 2, 3, 4, 5, 6]; let statuses = [true, false, true, true, false, true, false]; await setPriorityStatusByIndexes(indexes, statuses); @@ -487,7 +490,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { it('Shuffled: Actual(prioritized) > MaxNum(prioritized); Actual(regular) < MaxNum(regular)', async () => { // prettier-ignore - let indexes = [ 0, 1, 2, 3, 4, 5, 6]; + let indexes = [0, 1, 2, 3, 4, 5, 6]; let statuses = [true, false, true, true, false, true, true]; await setPriorityStatusByIndexes(indexes, statuses); @@ -506,7 +509,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { it('Shuffled: Actual(prioritized) < MaxNum(prioritized); Actual(regular) > MaxNum(regular)', async () => { // prettier-ignore - let indexes = [ 0, 1, 2, 3, 4, 5, 6, 7]; + let indexes = [0, 1, 2, 3, 4, 5, 6, 7]; let statuses = [true, false, false, false, false, true, true, false]; await setPriorityStatusByIndexes(indexes, statuses); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index f9ff6b36c..454403c8f 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -12,6 +12,7 @@ import { MockSlashIndicator, MockSlashIndicator__factory, StakingVesting__factory, + Maintenance__factory, } from '../../src/types'; import * as RoninValidatorSet from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../helpers/utils'; @@ -29,7 +30,7 @@ let validatorCandidates: SignerWithAddress[]; let currentValidatorSet: string[]; const slashFelonyAmount = 100; -const slashDoubleSignAmount = 1000; +const slashDoubleSignAmount = BigNumber.from(10).pow(18).mul(10); const maxValidatorNumber = 4; const maxPrioritizedValidatorNumber = 0; @@ -48,6 +49,7 @@ describe('Ronin Validator Set test', () => { validatorCandidates = validatorCandidates.slice(0, 5); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + const scheduleMaintenance = await new Maintenance__factory(deployer).deploy(); const nonce = await deployer.getTransactionCount(); const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 6 }); @@ -89,6 +91,7 @@ describe('Ronin Validator Set test', () => { slashIndicator.address, stakingContractAddr, stakingVesting.address, + scheduleMaintenance.address, maxValidatorNumber, maxValidatorCandidate, maxPrioritizedValidatorNumber, @@ -152,9 +155,15 @@ describe('Ronin Validator Set test', () => { for (let i = 0; i <= 3; i++) { await stakingContract .connect(validatorCandidates[i]) - .proposeValidator(validatorCandidates[i].address, validatorCandidates[i].address, 2_00, { - value: minValidatorBalance.add(i), - }); + .proposeValidator( + validatorCandidates[i].address, + validatorCandidates[i].address, + validatorCandidates[i].address, + 2_00, + { + value: minValidatorBalance.add(i), + } + ); } let tx: ContractTransaction; @@ -181,13 +190,19 @@ describe('Ronin Validator Set test', () => { it(`Should be able to wrap up epoch and pick top ${maxValidatorNumber} to be validators`, async () => { await stakingContract .connect(coinbase) - .proposeValidator(coinbase.address, treasury.address, 1_00 /* 1% */, { value: 100 }); + .proposeValidator(coinbase.address, coinbase.address, treasury.address, 1_00 /* 1% */, { value: 100 }); for (let i = 4; i < validatorCandidates.length; i++) { await stakingContract .connect(validatorCandidates[i]) - .proposeValidator(validatorCandidates[i].address, validatorCandidates[i].address, 2_00, { - value: minValidatorBalance.add(i), - }); + .proposeValidator( + validatorCandidates[i].address, + validatorCandidates[i].address, + validatorCandidates[i].address, + 2_00, + { + value: minValidatorBalance.add(i), + } + ); } expect((await roninValidatorSet.getValidatorCandidates()).length).eq(validatorCandidates.length + 1); @@ -240,7 +255,7 @@ describe('Ronin Validator Set test', () => { const balance = await treasury.getBalance(); await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); tx = await slashIndicator.slashMisdemeanor(coinbase.address); - await RoninValidatorSet.expects.emitValidatorSlashedEvent(tx!, coinbase.address, 0, 0); + expect(tx).emit(roninValidatorSet, 'ValidatorPunished').withArgs(coinbase.address, 0, 0); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); From fa7a5cb1da8bcc07861d756434e59fdc970ed518 Mon Sep 17 00:00:00 2001 From: Bao Date: Wed, 28 Sep 2022 13:46:09 +0700 Subject: [PATCH 164/190] Implement double signing slash (#19) * Draft impl slash double sign. Fix fixture. * Add mock contract * Fix assertion. Update mocks. Lint. * Fix sanity check of validator before slashing. Update test. * Fix test * Fix double signing test * Rename function * Fix assembly bug. Clean up. * Rename doubleSigningJailDuration --- contracts/interfaces/ISlashIndicator.sol | 44 ++- ....sol => MockRoninValidatorSetExtended.sol} | 9 +- contracts/mocks/MockSlashIndicator.sol | 8 +- .../mocks/MockSlashIndicatorExtended.sol | 14 + contracts/mocks/MockValidatorSet.sol | 6 +- contracts/{ => slash}/SlashIndicator.sol | 124 +++++++- src/config.ts | 2 + src/deploy/proxy/slash-indicator-proxy.ts | 1 + test/helpers/fixture.ts | 3 + test/helpers/ronin-validator-set.ts | 77 ++++- .../integration/ActionSlashValidators.test.ts | 12 +- test/integration/ActionSubmitReward.test.ts | 10 +- test/integration/ActionWrapUpEpoch.test.ts | 19 +- test/maintainance/Maintenance.test.ts | 51 ++-- test/slash/SlashIndicator.test.ts | 286 +++++++++++++----- ...oninValidatorSet-ArrangeValidators.test.ts | 10 +- test/validator/RoninValidatorSet.test.ts | 10 +- 17 files changed, 538 insertions(+), 148 deletions(-) rename contracts/mocks/{MockRoninValidatorSetExtends.sol => MockRoninValidatorSetExtended.sol} (87%) create mode 100644 contracts/mocks/MockSlashIndicatorExtended.sol rename contracts/{ => slash}/SlashIndicator.sol (67%) diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index aa78c365b..22337502f 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -13,6 +13,10 @@ interface ISlashIndicator { event SlashDoubleSignAmountUpdated(uint256 slashDoubleSignAmount); /// @dev Emiited when the duration of jailing felony updated event FelonyJailDurationUpdated(uint256 felonyJailDuration); + /// @dev Emiited when the constrain of ahead block in double signing updated + event DoubleSigningConstrainBlocksUpdated(uint256 doubleSigningConstrainBlocks); + /// @dev Emiited when the block number to jail the double signing validator to is updated + event DoubleSigningJailUntilBlockUpdated(uint256 doubleSigningJailUntilBlock); enum SlashType { UNKNOWN, @@ -21,6 +25,39 @@ interface ISlashIndicator { DOUBLE_SIGNING } + struct BlockHeader { + /// @dev Keccak hash of the parent block + bytes32 parentHash; + /// @dev Keccak hash of the ommers block + bytes32 ommersHash; + /// @dev Beneficiary address, i.e. mining fee recipient + address beneficiary; + /// @dev Keccak hash of the root of the state trie post execution + bytes32 stateRoot; + /// @dev Keccak hash of the root of transaction trie + bytes32 transactionsRoot; + /// @dev Keccak hash of the root node of recipients in the transaction + bytes32 receiptsRoot; + /// @dev Bloom filter of two fields log address and log topic in the receipts + bytes32[256] logsBloom; + /// @dev Scalar value of the difficulty of the previous block + uint256 difficulty; + /// @dev Scalar value of the number of ancestor blocks, i.e. block height + uint64 number; + /// @dev Scalar value of the current limit of gas usage per block + uint64 gasLimit; + /// @dev Scalar value of the total gas spent of the transactions in this block + uint64 gasUsed; + /// @dev Scalar value of the output of Unix's time() + uint64 timestamp; + /// @dev The signature of the validators + bytes32 extraData; + /// @dev A 256-bit hash which, combined with the `nonce`, proves that a sufficient amount of computation has been carried out on this block + bytes32 mixHash; + /// @dev A 64-bit value which, combined with the `mixHash`, proves that a sufficient amount of computation has been carried out on this block + uint8 nonce; + } + /** * @dev Slashes for unavailability by increasing the counter of validator with `_valAddr`. * If the counter passes the threshold, call the function from the validator contract. @@ -39,8 +76,13 @@ interface ISlashIndicator { * Requirements: * - Only coinbase can call this method * + * Emits the event `UnavailabilitySlashed` if the double signing evidence of the two headers valid */ - function slashDoubleSign(address _valAddr, bytes calldata _evidence) external; + function slashDoubleSign( + address _validatorAddr, + BlockHeader calldata _header1, + BlockHeader calldata _header2 + ) external; /** * @dev Sets the slash thresholds diff --git a/contracts/mocks/MockRoninValidatorSetExtends.sol b/contracts/mocks/MockRoninValidatorSetExtended.sol similarity index 87% rename from contracts/mocks/MockRoninValidatorSetExtends.sol rename to contracts/mocks/MockRoninValidatorSetExtended.sol index cb5163b0d..bb7f014b2 100644 --- a/contracts/mocks/MockRoninValidatorSetExtends.sol +++ b/contracts/mocks/MockRoninValidatorSetExtended.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.9; import "../validator/RoninValidatorSet.sol"; -contract MockRoninValidatorSetExtends is RoninValidatorSet { +contract MockRoninValidatorSetExtended is RoninValidatorSet { uint256[] internal _epochs; uint256[] internal _periods; @@ -59,6 +59,13 @@ contract MockRoninValidatorSetExtends is RoninValidatorSet { } } + function addValidators(address[] calldata _addrs) public { + for (uint _i = 0; _i < _addrs.length; _i++) { + _validator[_i] = _addrs[_i]; + _validatorMap[_addrs[_i]] = true; + } + } + function arrangeValidatorCandidates(address[] memory _candidates, uint _newValidatorCount) external view diff --git a/contracts/mocks/MockSlashIndicator.sol b/contracts/mocks/MockSlashIndicator.sol index bc2382ae6..09aee9eb4 100644 --- a/contracts/mocks/MockSlashIndicator.sol +++ b/contracts/mocks/MockSlashIndicator.sol @@ -35,8 +35,6 @@ contract MockSlashIndicator is ISlashIndicator { function slash(address _valAddr) external override {} - function slashDoubleSign(address _valAddr, bytes calldata _evidence) external override {} - function getUnavailabilityThresholds() external view @@ -69,4 +67,10 @@ contract MockSlashIndicator is ISlashIndicator { override returns (uint256 _felonyThreshold, uint256 _misdemeanorThreshold) {} + + function slashDoubleSign( + address _validatorAddr, + BlockHeader calldata _header1, + BlockHeader calldata _header2 + ) external override {} } diff --git a/contracts/mocks/MockSlashIndicatorExtended.sol b/contracts/mocks/MockSlashIndicatorExtended.sol new file mode 100644 index 000000000..d4b4aaef7 --- /dev/null +++ b/contracts/mocks/MockSlashIndicatorExtended.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../slash/SlashIndicator.sol"; + +contract MockSlashIndicatorExtended is SlashIndicator { + function _validateEvidence( + BlockHeader memory, /*_header1*/ + BlockHeader memory /*_header2*/ + ) internal pure override returns (bool _validEvidence) { + return true; + } +} diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index dec511480..ac528f8d3 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -112,5 +112,9 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { function getPriorityStatus(address _addr) external view override returns (bool) {} - function isValidator(address _addr) external view override returns (bool) {} + function isValidator( + address /* _addr */ + ) external pure override returns (bool) { + return true; + } } diff --git a/contracts/SlashIndicator.sol b/contracts/slash/SlashIndicator.sol similarity index 67% rename from contracts/SlashIndicator.sol rename to contracts/slash/SlashIndicator.sol index c78e051af..544044b11 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/slash/SlashIndicator.sol @@ -3,14 +3,16 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "./interfaces/ISlashIndicator.sol"; -import "./extensions/HasValidatorContract.sol"; -import "./extensions/HasMaintenanceContract.sol"; -import "./libraries/Math.sol"; +import "../interfaces/ISlashIndicator.sol"; +import "../extensions/HasValidatorContract.sol"; +import "../extensions/HasMaintenanceContract.sol"; +import "../libraries/Math.sol"; contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenanceContract, Initializable { using Math for uint256; + uint8 public constant VALIDATING_DOUBLE_SIGN_PROOF_PRECOMPILE = 0x20; + /// @dev Mapping from validator address => period index => unavailability indicator mapping(address => mapping(uint256 => uint256)) internal _unavailabilityIndicator; /// @dev Maping from validator address => period index => slash type @@ -19,6 +21,9 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenance /// @dev The last block that a validator is slashed uint256 public lastSlashedBlock; + /// @dev The number of blocks that the current block can be ahead of the double signed blocks + uint256 public doubleSigningConstrainBlocks; + /// @dev The threshold to slash when validator is unavailability reaches misdemeanor uint256 public misdemeanorThreshold; /// @dev The threshold to slash when validator is unavailability reaches felony @@ -28,8 +33,10 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenance uint256 public slashFelonyAmount; /// @dev The amount of RON to slash double sign. uint256 public slashDoubleSignAmount; - /// @dev The block duration to jail validator that reaches felony thresold. + /// @dev The block duration to jail a validator that reaches felony thresold. uint256 public felonyJailDuration; + /// @dev The block number that the punished validator will be jailed until, due to double signing. + uint256 public doubleSigningJailUntilBlock; modifier onlyCoinbase() { require(msg.sender == block.coinbase, "SlashIndicator: method caller must be coinbase"); @@ -59,7 +66,8 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenance uint256 _felonyThreshold, uint256 _slashFelonyAmount, uint256 _slashDoubleSignAmount, - uint256 _felonyJailBlocks + uint256 _felonyJailBlocks, + uint256 _doubleSigningConstrainBlocks ) external initializer { _setValidatorContract(__validatorContract); _setMaintenanceContract(__maintenanceContract); @@ -67,6 +75,8 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenance _setSlashFelonyAmount(_slashFelonyAmount); _setSlashDoubleSignAmount(_slashDoubleSignAmount); _setFelonyJailDuration(_felonyJailBlocks); + _setDoubleSigningConstrainBlocks(_doubleSigningConstrainBlocks); + _setDoubleSigningJailUntilBlock(type(uint256).max); } /////////////////////////////////////////////////////////////////////////////////////// @@ -77,7 +87,7 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenance * @inheritdoc ISlashIndicator */ function slash(address _validatorAddr) external override onlyCoinbase oncePerBlock { - if (msg.sender == _validatorAddr || _maintenanceContract.maintaining(_validatorAddr, block.number)) { + if (!_shouldSlash(_validatorAddr)) { return; } @@ -110,11 +120,26 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenance */ function slashDoubleSign( address _validatorAddr, - bytes calldata /* _evidence */ - ) external override onlyCoinbase { - bool _proved = false; // Proves the `_evidence` is right - if (_proved) { - _validatorContract.slash(_validatorAddr, type(uint256).max, slashDoubleSignAmount); + BlockHeader memory _header1, + BlockHeader memory _header2 + ) external override onlyCoinbase oncePerBlock { + if (!_shouldSlash(_validatorAddr)) { + return; + } + + if ( + block.number > _header1.number + doubleSigningConstrainBlocks || + block.number > _header2.number + doubleSigningConstrainBlocks || + _header1.parentHash != _header2.parentHash + ) { + return; + } + + if (_validateEvidence(_header1, _header2)) { + uint256 _period = _validatorContract.periodOf(block.number); + _unavailabilitySlashed[_validatorAddr][_period] = SlashType.DOUBLE_SIGNING; + emit UnavailabilitySlashed(_validatorAddr, SlashType.DOUBLE_SIGNING, _period); + _validatorContract.slash(_validatorAddr, doubleSigningJailUntilBlock, slashDoubleSignAmount); } } @@ -246,4 +271,79 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenance felonyJailDuration = _felonyJailDuration; emit FelonyJailDurationUpdated(_felonyJailDuration); } + + /** + * @dev Sets the double signing constrain blocks + */ + function _setDoubleSigningConstrainBlocks(uint256 _doubleSigningConstrainBlocks) internal { + doubleSigningConstrainBlocks = _doubleSigningConstrainBlocks; + emit DoubleSigningConstrainBlocksUpdated(_doubleSigningConstrainBlocks); + } + + /** + * @dev Sets the double signing jail until block number + */ + function _setDoubleSigningJailUntilBlock(uint256 _doubleSigningJailUntilBlock) internal { + doubleSigningJailUntilBlock = _doubleSigningJailUntilBlock; + emit DoubleSigningJailUntilBlockUpdated(_doubleSigningJailUntilBlock); + } + + /** + * @dev Sanity check the address to be slashed + */ + function _shouldSlash(address _addr) internal view returns (bool) { + return + (msg.sender != _addr) && + _validatorContract.isValidator(_addr) && + !_maintenanceContract.maintaining(_addr, block.number); + } + + /** + * @dev Validate the two submitted block header if they are produced by the same address + * + * Note: The recover process is done by pre-compiled contract. This function is marked as + * virtual for implementing mocking contract for testing purpose. + */ + function _validateEvidence(BlockHeader memory _header1, BlockHeader memory _header2) + internal + view + virtual + returns (bool _validEvidence) + { + bytes memory _input = bytes.concat(_packBlockHeader(_header1), _packBlockHeader(_header2)); + uint _inputSize = _input.length; + uint[1] memory _output; + + bytes memory _revertReason = "SlashIndicator: call to precompile fails"; + + assembly { + if iszero(staticcall(gas(), VALIDATING_DOUBLE_SIGN_PROOF_PRECOMPILE, _input, _inputSize, _output, 0x20)) { + revert(add(32, _revertReason), mload(_revertReason)) + } + } + + return (_output[0] != 0); + } + + /** + * @dev Packing the block header struct into a single bytes. Helper function for validating + * evidence function. + */ + function _packBlockHeader(BlockHeader memory _header) private pure returns (bytes memory) { + return + abi.encode( + _header.parentHash, + _header.ommersHash, + _header.beneficiary, + _header.stateRoot, + _header.transactionsRoot, + _header.receiptsRoot, + _header.logsBloom, + _header.difficulty, + _header.number, + _header.gasLimit, + _header.gasUsed, + _header.timestamp + ); + } } diff --git a/src/config.ts b/src/config.ts index 169400caa..dfb01c9d1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -56,6 +56,7 @@ export interface SlashIndicatorArguments { slashFelonyAmount?: BigNumberish; slashDoubleSignAmount?: BigNumberish; felonyJailBlocks?: BigNumberish; + doubleSigningConstrainBlocks?: BigNumberish; } export interface SlashIndicatorConfig { @@ -126,6 +127,7 @@ export const slashIndicatorConf: SlashIndicatorConfig = { slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), // 1 RON slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), // 10 RON felonyJailBlocks: 28800 * 2, // jails for 2 days + doubleSigningConstrainBlocks: 28800, }, [Network.Testnet]: undefined, [Network.Mainnet]: undefined, diff --git a/src/deploy/proxy/slash-indicator-proxy.ts b/src/deploy/proxy/slash-indicator-proxy.ts index 4aadb8e9e..86d1f14d0 100644 --- a/src/deploy/proxy/slash-indicator-proxy.ts +++ b/src/deploy/proxy/slash-indicator-proxy.ts @@ -18,6 +18,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme slashIndicatorConf[network.name]!.slashFelonyAmount, slashIndicatorConf[network.name]!.slashDoubleSignAmount, slashIndicatorConf[network.name]!.felonyJailBlocks, + slashIndicatorConf[network.name]!.doubleSigningConstrainBlocks, ]); await deploy('SlashIndicatorProxy', { diff --git a/test/helpers/fixture.ts b/test/helpers/fixture.ts index e8780ec65..e4c64cfed 100644 --- a/test/helpers/fixture.ts +++ b/test/helpers/fixture.ts @@ -32,6 +32,7 @@ export const defaultTestConfig = { felonyThreshold: 10, slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), + doubleSigningConstrainBlocks: 28800, maxValidatorNumber: 4, maxPrioritizedValidatorNumber: 0, @@ -72,6 +73,8 @@ export const initTest = (id: string) => slashFelonyAmount: options?.slashFelonyAmount ?? defaultTestConfig.slashFelonyAmount, slashDoubleSignAmount: options?.slashDoubleSignAmount ?? defaultTestConfig.slashDoubleSignAmount, felonyJailBlocks: options?.felonyJailBlocks ?? defaultTestConfig.felonyJailBlocks, + doubleSigningConstrainBlocks: + options?.doubleSigningConstrainBlocks ?? defaultTestConfig.doubleSigningConstrainBlocks, }; roninValidatorSetConf[network.name] = { maxValidatorNumber: options?.maxValidatorNumber ?? defaultTestConfig.maxValidatorNumber, diff --git a/test/helpers/ronin-validator-set.ts b/test/helpers/ronin-validator-set.ts index 8a411f164..700227af4 100644 --- a/test/helpers/ronin-validator-set.ts +++ b/test/helpers/ronin-validator-set.ts @@ -1,11 +1,86 @@ +import { ethers, network } from 'hardhat'; + import { expect } from 'chai'; -import { BigNumberish, ContractTransaction } from 'ethers'; +import { BigNumber, BigNumberish, ContractTransaction } from 'ethers'; import { expectEvent } from './utils'; import { RoninValidatorSet__factory } from '../../src/types'; const contractInterface = RoninValidatorSet__factory.createInterface(); +export class EpochController { + readonly minOffset: number; + readonly numberOfBlocksInEpoch: number; + readonly numberOfEpochsInPeriod: number; + readonly numberOfBlocksInPeriod: number; + + constructor(minOffset: number, numberOfBlocksInEpoch: number, numberOfEpochsInPeriod: number) { + this.minOffset = minOffset; + this.numberOfBlocksInEpoch = numberOfBlocksInEpoch; + this.numberOfEpochsInPeriod = numberOfEpochsInPeriod; + this.numberOfBlocksInPeriod = numberOfBlocksInEpoch * numberOfEpochsInPeriod; + } + + calculateStartOfEpoch(block: number): BigNumber { + return BigNumber.from( + Math.floor((block + this.minOffset + this.numberOfBlocksInEpoch - 1) / this.numberOfBlocksInEpoch) * + this.numberOfBlocksInEpoch + ); + } + + diffToEndEpoch(block: BigNumberish): BigNumber { + return BigNumber.from(this.numberOfBlocksInEpoch).sub(BigNumber.from(block).mod(this.numberOfBlocksInEpoch)).sub(1); + } + + diffToEndPeriod(block: BigNumberish): BigNumber { + return BigNumber.from(this.numberOfBlocksInPeriod) + .sub(BigNumber.from(block).mod(this.numberOfBlocksInPeriod)) + .sub(1); + } + + calculateEndOfEpoch(block: BigNumberish): BigNumber { + return BigNumber.from(block).add(this.diffToEndEpoch(block)); + } + + calculateEndOfPeriod(block: BigNumberish): BigNumber { + return BigNumber.from(block).add(this.diffToEndPeriod(block)); + } + + calculatePeriodOf(block: BigNumberish): BigNumber { + return BigNumber.from(block).div(BigNumber.from(this.numberOfBlocksInPeriod)).add(1); + } + + async currentPeriod(): Promise { + return this.calculatePeriodOf(await ethers.provider.getBlockNumber()); + } + + async mineToBeforeEndOfEpoch() { + let number = this.diffToEndEpoch(await ethers.provider.getBlockNumber()).sub(1); + if (number.lt(0)) { + number = number.add(this.numberOfBlocksInEpoch); + } + return network.provider.send('hardhat_mine', [ethers.utils.hexStripZeros(number.toHexString())]); + } + + async mineToBeforeEndOfPeriod() { + let number = this.diffToEndPeriod(await ethers.provider.getBlockNumber()).sub(1); + if (number.lt(0)) { + number = number.add(this.numberOfBlocksInPeriod); + } + return network.provider.send('hardhat_mine', [ethers.utils.hexStripZeros(number.toHexString())]); + } + + async mineToBeginOfNewEpoch() { + await this.mineToBeforeEndOfEpoch(); + return network.provider.send('hardhat_mine', ['0x2']); + } + + async mineToBeginOfNewPeriod() { + await this.mineToBeforeEndOfPeriod(); + return network.provider.send('hardhat_mine', ['0x2']); + } +} + export const expects = { emitRewardDeprecatedEvent: async function ( tx: ContractTransaction, diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index a83cd3a4a..050ad7ed7 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -9,8 +9,8 @@ import { SlashIndicator__factory, Staking, Staking__factory, - MockRoninValidatorSetExtends__factory, - MockRoninValidatorSetExtends, + MockRoninValidatorSetExtended__factory, + MockRoninValidatorSetExtended, } from '../../src/types'; import { expects as RoninValidatorSetExpects } from '../helpers/ronin-validator-set'; @@ -20,7 +20,7 @@ import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; let slashContract: SlashIndicator; let stakingContract: Staking; -let validatorContract: MockRoninValidatorSetExtends; +let validatorContract: MockRoninValidatorSetExtended; let governanceAdmin: GovernanceAdminInterface; let coinbase: SignerWithAddress; @@ -57,9 +57,9 @@ describe('[Integration] Slash validators', () => { slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); - validatorContract = MockRoninValidatorSetExtends__factory.connect(validatorContractAddress, deployer); + validatorContract = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); - const mockValidatorLogic = await new MockRoninValidatorSetExtends__factory(deployer).deploy(); + const mockValidatorLogic = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); await mockValidatorLogic.deployed(); governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); await network.provider.send('hardhat_mine', [ @@ -75,6 +75,8 @@ describe('[Integration] Slash validators', () => { const currentBlock = await ethers.provider.getBlockNumber(); period = await validatorContract.periodOf(currentBlock); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + + await validatorContract.addValidators([1, 2, 3].map((_) => validatorCandidates[_].address)); }); describe('Slash misdemeanor validator', async () => { diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index 527ffaca5..714bda1fc 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -9,15 +9,15 @@ import { SlashIndicator__factory, Staking, Staking__factory, - MockRoninValidatorSetExtends__factory, - MockRoninValidatorSetExtends, + MockRoninValidatorSetExtended__factory, + MockRoninValidatorSetExtended, } from '../../src/types'; import { mineBatchTxs } from '../helpers/utils'; import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; let slashContract: SlashIndicator; let stakingContract: Staking; -let validatorContract: MockRoninValidatorSetExtends; +let validatorContract: MockRoninValidatorSetExtended; let governanceAdmin: GovernanceAdminInterface; let coinbase: SignerWithAddress; @@ -52,9 +52,9 @@ describe('[Integration] Submit Block Reward', () => { slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); - validatorContract = MockRoninValidatorSetExtends__factory.connect(validatorContractAddress, deployer); + validatorContract = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); - const mockValidatorLogic = await new MockRoninValidatorSetExtends__factory(deployer).deploy(); + const mockValidatorLogic = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); await mockValidatorLogic.deployed(); governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); }); diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index ab21cd26e..1f674efb0 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -8,8 +8,8 @@ import { SlashIndicator__factory, Staking, Staking__factory, - MockRoninValidatorSetExtends__factory, - MockRoninValidatorSetExtends, + MockRoninValidatorSetExtended__factory, + MockRoninValidatorSetExtended, } from '../../src/types'; import { expects as StakingExpects } from '../helpers/reward-calculation'; import { expects as ValidatorSetExpects } from '../helpers/ronin-validator-set'; @@ -18,7 +18,7 @@ import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; let slashContract: SlashIndicator; let stakingContract: Staking; -let validatorContract: MockRoninValidatorSetExtends; +let validatorContract: MockRoninValidatorSetExtended; let governanceAdmin: GovernanceAdminInterface; let coinbase: SignerWithAddress; @@ -52,9 +52,9 @@ describe('[Integration] Wrap up epoch', () => { }); slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); - validatorContract = MockRoninValidatorSetExtends__factory.connect(validatorContractAddress, deployer); + validatorContract = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); - const mockValidatorLogic = await new MockRoninValidatorSetExtends__factory(deployer).deploy(); + const mockValidatorLogic = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); await mockValidatorLogic.deployed(); governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); }); @@ -117,12 +117,14 @@ describe('[Integration] Wrap up epoch', () => { await validatorContract.connect(coinbase).wrapUpEpoch(); }); - coinbase = validators[3]; - await network.provider.send('hardhat_setCoinbase', [coinbase.address]); - validatorContract = validatorContract.connect(coinbase); + await network.provider.send('hardhat_setCoinbase', [validators[3].address]); + validatorContract = validatorContract.connect(validators[3]); await validatorContract.submitBlockReward({ value: blockRewardAmount, }); + + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + validatorContract = validatorContract.connect(coinbase); }); describe('Wrap up epoch: at the end of the epoch', async () => { @@ -161,6 +163,7 @@ describe('[Integration] Wrap up epoch', () => { describe('Wrap up epoch: at the end of the period', async () => { before(async () => { + await validatorContract.addValidators(validators.map((v) => v.address)); await Promise.all(validators.map((v) => slashContract.connect(coinbase).slash(v.address))); }); diff --git a/test/maintainance/Maintenance.test.ts b/test/maintainance/Maintenance.test.ts index 44a721151..9e0e52611 100644 --- a/test/maintainance/Maintenance.test.ts +++ b/test/maintainance/Maintenance.test.ts @@ -14,6 +14,7 @@ import { Staking__factory, } from '../../src/types'; import { initTest } from '../helpers/fixture'; +import { EpochController } from '../helpers/ronin-validator-set'; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; @@ -25,6 +26,8 @@ let slashContract: SlashIndicator; let stakingContract: Staking; let validatorContract: RoninValidatorSet; +let localEpochController: EpochController; + const misdemeanorThreshold = 50; const felonyThreshold = 150; const maxValidatorNumber = 4; @@ -39,21 +42,6 @@ let startedAtBlock: BigNumberish = 0; let endedAtBlock: BigNumberish = 0; let currentBlock: number; -const calculateStartOfEpoch = (block: number) => - BigNumber.from( - Math.floor((block + minOffset + numberOfBlocksInEpoch - 1) / numberOfBlocksInEpoch) * numberOfBlocksInEpoch - ); -const diffToEndEpoch = (block: BigNumberish) => - BigNumber.from(numberOfBlocksInEpoch).sub(BigNumber.from(block).mod(numberOfBlocksInEpoch)).sub(1); -const calculateEndOfEpoch = (block: BigNumberish) => BigNumber.from(block).add(diffToEndEpoch(block)); -const mineToBeforeEndOfEpoch = async () => { - let number = diffToEndEpoch(await ethers.provider.getBlockNumber()).sub(1); - if (number.lt(0)) { - number = number.add(numberOfBlocksInEpoch); - } - return network.provider.send('hardhat_mine', [ethers.utils.hexStripZeros(number.toHexString())]); -}; - describe('Maintenance test', () => { before(async () => { [deployer, coinbase, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); @@ -85,7 +73,10 @@ describe('Maintenance test', () => { await network.provider.send('hardhat_mine', [ ethers.utils.hexStripZeros(BigNumber.from(numberOfBlocksInEpoch * numberOfEpochsInPeriod).toHexString()), ]); - await mineToBeforeEndOfEpoch(); + + localEpochController = new EpochController(minOffset, numberOfBlocksInEpoch, numberOfEpochsInPeriod); + + await localEpochController.mineToBeforeEndOfEpoch(); await validatorContract.connect(coinbase).wrapUpEpoch(); expect(await validatorContract.getValidators()).eql(validatorCandidates.map((_) => _.address)); @@ -158,8 +149,8 @@ describe('Maintenance test', () => { }); it('Should be not able to schedule maintenance when the start block is not at the start of an epoch', async () => { - startedAtBlock = calculateStartOfEpoch(currentBlock).add(1); - endedAtBlock = calculateEndOfEpoch(startedAtBlock.add(minMaintenanceBlockPeriod)); + startedAtBlock = localEpochController.calculateStartOfEpoch(currentBlock).add(1); + endedAtBlock = localEpochController.calculateEndOfEpoch(startedAtBlock.add(minMaintenanceBlockPeriod)); expect(startedAtBlock.mod(numberOfBlocksInEpoch)).not.eq(0); expect(endedAtBlock.mod(numberOfBlocksInEpoch)).eq(numberOfBlocksInEpoch - 1); @@ -172,8 +163,8 @@ describe('Maintenance test', () => { it('Should be not able to schedule maintenance when the end block is not at the end of an epoch', async () => { currentBlock = (await ethers.provider.getBlockNumber()) + 1; - startedAtBlock = calculateStartOfEpoch(currentBlock); - endedAtBlock = calculateEndOfEpoch(startedAtBlock.add(minMaintenanceBlockPeriod)).add(1); + startedAtBlock = localEpochController.calculateStartOfEpoch(currentBlock); + endedAtBlock = localEpochController.calculateEndOfEpoch(startedAtBlock.add(minMaintenanceBlockPeriod)).add(1); expect(startedAtBlock.mod(numberOfBlocksInEpoch)).eq(0); expect(endedAtBlock.mod(numberOfBlocksInEpoch)).not.eq(numberOfBlocksInEpoch - 1); @@ -200,8 +191,10 @@ describe('Maintenance test', () => { it('Should be able to schedule maintenance using validator admin account', async () => { currentBlock = (await ethers.provider.getBlockNumber()) + 1; - startedAtBlock = calculateStartOfEpoch(currentBlock).add(numberOfBlocksInEpoch); - endedAtBlock = calculateEndOfEpoch(BigNumber.from(startedAtBlock).add(minMaintenanceBlockPeriod)); + startedAtBlock = localEpochController.calculateStartOfEpoch(currentBlock).add(numberOfBlocksInEpoch); + endedAtBlock = localEpochController.calculateEndOfEpoch( + BigNumber.from(startedAtBlock).add(minMaintenanceBlockPeriod) + ); const tx = await maintenanceContract .connect(validatorCandidates[0]) @@ -235,13 +228,13 @@ describe('Maintenance test', () => { }); it('Should the validator still appear in the validator list since it is not maintenance time yet', async () => { - await mineToBeforeEndOfEpoch(); + await localEpochController.mineToBeforeEndOfEpoch(); await validatorContract.connect(coinbase).wrapUpEpoch(); expect(await validatorContract.getValidators()).eql(validatorCandidates.map((_) => _.address)); }); it('Should the validator not appear in the validator list since the maintenance is started', async () => { - await mineToBeforeEndOfEpoch(); + await localEpochController.mineToBeforeEndOfEpoch(); await validatorContract.connect(coinbase).wrapUpEpoch(); expect(await validatorContract.getValidators()).eql(validatorCandidates.slice(2).map((_) => _.address)); }); @@ -267,15 +260,15 @@ describe('Maintenance test', () => { }); it('Should the validator appear in the validator list since the maintenance time is ended', async () => { - await mineToBeforeEndOfEpoch(); + await localEpochController.mineToBeforeEndOfEpoch(); await validatorContract.connect(coinbase).wrapUpEpoch(); expect(await validatorContract.getValidators()).eql(validatorCandidates.map((_) => _.address)); }); it('Should not be able to schedule maintenance twice in a period', async () => { currentBlock = (await ethers.provider.getBlockNumber()) + 1; - startedAtBlock = calculateStartOfEpoch(currentBlock); - endedAtBlock = calculateEndOfEpoch(startedAtBlock); + startedAtBlock = localEpochController.calculateStartOfEpoch(currentBlock); + endedAtBlock = localEpochController.calculateEndOfEpoch(startedAtBlock); await expect( maintenanceContract .connect(validatorCandidates[0]) @@ -294,8 +287,8 @@ describe('Maintenance test', () => { it('Should be able to schedule in the next period', async () => { currentBlock = (await ethers.provider.getBlockNumber()) + 1; - startedAtBlock = calculateStartOfEpoch(currentBlock); - endedAtBlock = calculateEndOfEpoch(startedAtBlock); + startedAtBlock = localEpochController.calculateStartOfEpoch(currentBlock); + endedAtBlock = localEpochController.calculateEndOfEpoch(startedAtBlock); await maintenanceContract .connect(validatorCandidates[0]) .schedule(validatorCandidates[0].address, startedAtBlock, endedAtBlock); diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index be203a7b1..242ad59bf 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -3,22 +3,47 @@ import { expect } from 'chai'; import { ethers, network } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { SlashIndicator, SlashIndicator__factory, MockValidatorSet__factory, MockValidatorSet } from '../../src/types'; +import { + MockSlashIndicatorExtended, + MockSlashIndicatorExtended__factory, + RoninValidatorSet, + RoninValidatorSet__factory, + Staking, + Staking__factory, +} from '../../src/types'; +import { BlockHeaderStruct } from '../../src/types/ISlashIndicator'; import { SlashType } from '../../src/script/slash-indicator'; import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; +import { EpochController } from '../helpers/ronin-validator-set'; -let slashContract: SlashIndicator; +let slashContract: MockSlashIndicatorExtended; +let mockSlashLogic: MockSlashIndicatorExtended; +let stakingContract: Staking; +let governanceAdmin: GovernanceAdminInterface; +let coinbase: SignerWithAddress; let deployer: SignerWithAddress; -let governanceAdmin: SignerWithAddress; -let mockValidatorsContract: MockValidatorSet; +let governor: SignerWithAddress; +let validatorContract: RoninValidatorSet; let vagabond: SignerWithAddress; -let coinbases: SignerWithAddress[]; +let validatorCandidates: SignerWithAddress[]; let localIndicators: number[]; -let felonyThreshold: number; -let misdemeanorThreshold: number; -const maxValidatorCandidate = 10; +let localEpochController: EpochController; + +const misdemeanorThreshold = 5; +const felonyThreshold = 10; +const maxValidatorNumber = 21; +const maxValidatorCandidate = 50; +const numberOfBlocksInEpoch = 600; +const numberOfEpochsInPeriod = 48; +const minValidatorBalance = BigNumber.from(100); + +const slashFelonyAmount = BigNumber.from(2); +const slashDoubleSignAmount = BigNumber.from(5); + +const minOffset = 200; +const doubleSigningConstrainBlocks = BigNumber.from(28800); const increaseLocalCounterForValidatorAt = (idx: number, value?: number) => { value = value ?? 1; @@ -34,47 +59,84 @@ const resetLocalCounterForValidatorAt = (idx: number) => { }; const validateIndicatorAt = async (idx: number) => { - expect(localIndicators[idx]).to.eq(await slashContract.currentUnavailabilityIndicator(coinbases[idx].address)); + expect(await slashContract.currentUnavailabilityIndicator(validatorCandidates[idx].address)).to.eq( + localIndicators[idx] + ); +}; + +const generateDefaultBlockHeader = (blockHeight: number): BlockHeaderStruct => { + return { + parentHash: ethers.constants.HashZero, + ommersHash: ethers.constants.HashZero, + beneficiary: ethers.constants.AddressZero, + stateRoot: ethers.constants.HashZero, + transactionsRoot: ethers.constants.HashZero, + receiptsRoot: ethers.constants.HashZero, + logsBloom: new Array(256).fill(ethers.constants.HashZero), + difficulty: 1, + number: blockHeight, + gasLimit: 1, + gasUsed: 1, + timestamp: 1, + extraData: ethers.constants.HashZero, + mixHash: ethers.constants.HashZero, + nonce: 1, + }; }; describe('Slash indicator test', () => { before(async () => { - [deployer, governanceAdmin, vagabond, ...coinbases] = await ethers.getSigners(); - localIndicators = Array(coinbases.length).fill(0); - - const { slashContractAddress, stakingContractAddress, stakingVestingContractAddress } = await initTest( - 'SlashIndicator' - )({ - misdemeanorThreshold: 5, - felonyThreshold: 10, - slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), // 1 RON - slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), // 10 RON - felonyJailBlocks: 28800 * 2, - maxValidatorCandidate, - governanceAdmin: governanceAdmin.address, - }); + [deployer, coinbase, governor, vagabond, ...validatorCandidates] = await ethers.getSigners(); + governanceAdmin = new GovernanceAdminInterface(governor); + + const { slashContractAddress, stakingContractAddress, validatorContractAddress } = await initTest('SlashIndicator')( + { + governanceAdmin: governor.address, + misdemeanorThreshold, + felonyThreshold, + maxValidatorNumber, + maxValidatorCandidate, + numberOfBlocksInEpoch, + numberOfEpochsInPeriod, + minValidatorBalance, + slashFelonyAmount, + slashDoubleSignAmount, + } + ); - slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); + stakingContract = Staking__factory.connect(stakingContractAddress, deployer); + validatorContract = RoninValidatorSet__factory.connect(validatorContractAddress, deployer); + slashContract = MockSlashIndicatorExtended__factory.connect(slashContractAddress, deployer); + + mockSlashLogic = await new MockSlashIndicatorExtended__factory(deployer).deploy(); + await mockSlashLogic.deployed(); + governanceAdmin.upgrade(slashContractAddress, mockSlashLogic.address); + + validatorCandidates = validatorCandidates.slice(0, maxValidatorNumber); + for (let i = 0; i < maxValidatorNumber; i++) { + await stakingContract + .connect(validatorCandidates[i]) + .proposeValidator( + validatorCandidates[i].address, + validatorCandidates[i].address, + validatorCandidates[i].address, + 1, + { value: minValidatorBalance.mul(2).add(maxValidatorNumber).sub(i) } + ); + } - // Sets the new validator contract instead of upgrading because the storage is mismatched - mockValidatorsContract = await new MockValidatorSet__factory(deployer).deploy( - stakingContractAddress, - slashContractAddress, - stakingVestingContractAddress, - maxValidatorCandidate, - 600, - 48 - ); - await mockValidatorsContract.deployed(); + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + await network.provider.send('hardhat_mine', [ + ethers.utils.hexStripZeros(BigNumber.from(numberOfBlocksInEpoch * numberOfEpochsInPeriod).toHexString()), + ]); - await new GovernanceAdminInterface(governanceAdmin).functionDelegateCall( - slashContract.address, - slashContract.interface.encodeFunctionData('setValidatorContract', [mockValidatorsContract.address]) - ); + localEpochController = new EpochController(minOffset, numberOfBlocksInEpoch, numberOfEpochsInPeriod); + await localEpochController.mineToBeforeEndOfEpoch(); - [misdemeanorThreshold, felonyThreshold] = (await slashContract.getUnavailabilityThresholds()).map((_) => - _.toNumber() - ); + await validatorContract.connect(coinbase).wrapUpEpoch(); + expect(await validatorContract.getValidators()).eql(validatorCandidates.map((_) => _.address)); + + localIndicators = Array(validatorCandidates.length).fill(0); }); after(async () => { @@ -84,7 +146,7 @@ describe('Slash indicator test', () => { describe('Single flow test', async () => { describe('Unauthorized test', async () => { it('Should non-coinbase cannot call slash', async () => { - await expect(slashContract.connect(vagabond).slash(coinbases[0].address)).to.revertedWith( + await expect(slashContract.connect(vagabond).slash(validatorCandidates[0].address)).to.revertedWith( 'SlashIndicator: method caller must be coinbase' ); }); @@ -95,18 +157,19 @@ describe('Slash indicator test', () => { const slasherIdx = 0; const slasheeIdx = 1; - await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); + await network.provider.send('hardhat_setCoinbase', [validatorCandidates[slasherIdx].address]); - let tx = await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); - expect(tx).to.not.emit(slashContract, 'UnavailabilitySlashed'); + let tx = await slashContract + .connect(validatorCandidates[slasherIdx]) + .slash(validatorCandidates[slasheeIdx].address); + await expect(tx).to.not.emit(slashContract, 'UnavailabilitySlashed'); setLocalCounterForValidatorAt(slasheeIdx, 1); await validateIndicatorAt(slasheeIdx); }); it('Should validator not be able to slash themselves', async () => { const slasherIdx = 0; - let tx = await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasherIdx].address); - expect(tx).to.not.emit(slashContract, 'UnavailabilitySlashed'); + await slashContract.connect(validatorCandidates[slasherIdx]).slash(validatorCandidates[slasherIdx].address); resetLocalCounterForValidatorAt(slasherIdx); await validateIndicatorAt(slasherIdx); @@ -116,8 +179,8 @@ describe('Slash indicator test', () => { const slasherIdx = 0; const slasheeIdx = 2; await network.provider.send('evm_setAutomine', [false]); - await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); - let tx = slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); + await slashContract.connect(validatorCandidates[slasherIdx]).slash(validatorCandidates[slasheeIdx].address); + let tx = slashContract.connect(validatorCandidates[slasherIdx]).slash(validatorCandidates[slasheeIdx].address); await expect(tx).to.be.revertedWith( 'SlashIndicator: cannot slash a validator twice or slash more than one validator in one block' ); @@ -133,8 +196,8 @@ describe('Slash indicator test', () => { const slasheeIdx1 = 1; const slasheeIdx2 = 2; await network.provider.send('evm_setAutomine', [false]); - await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx1].address); - let tx = slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx2].address); + await slashContract.connect(validatorCandidates[slasherIdx]).slash(validatorCandidates[slasheeIdx1].address); + let tx = slashContract.connect(validatorCandidates[slasherIdx]).slash(validatorCandidates[slasheeIdx2].address); await expect(tx).to.be.revertedWith( 'SlashIndicator: cannot slash a validator twice or slash more than one validator in one block' ); @@ -153,29 +216,35 @@ describe('Slash indicator test', () => { let tx; const slasherIdx = 1; const slasheeIdx = 3; - await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); + await network.provider.send('hardhat_setCoinbase', [validatorCandidates[slasherIdx].address]); for (let i = 0; i < misdemeanorThreshold; i++) { - tx = await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); + tx = await slashContract + .connect(validatorCandidates[slasherIdx]) + .slash(validatorCandidates[slasheeIdx].address); } - expect(tx) + + let _period = await localEpochController.currentPeriod(); + await expect(tx) .to.emit(slashContract, 'UnavailabilitySlashed') - .withArgs(coinbases[1].address, SlashType.MISDEMEANOR); + .withArgs(validatorCandidates[slasheeIdx].address, SlashType.MISDEMEANOR, _period); setLocalCounterForValidatorAt(slasheeIdx, misdemeanorThreshold); await validateIndicatorAt(slasheeIdx); }); - it('Should not sync with validator set when the indicator counter is in between misdemeanor (tier-1) and felony (tier-2) thresholds ', async () => { + it('Should not sync with validator set when the indicator counter is in between misdemeanor (tier-1) and felony (tier-2) thresholds', async () => { let tx; const slasherIdx = 1; const slasheeIdx = 3; - await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); + await network.provider.send('hardhat_setCoinbase', [validatorCandidates[slasherIdx].address]); - tx = await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); + tx = await slashContract + .connect(validatorCandidates[slasherIdx]) + .slash(validatorCandidates[slasheeIdx].address); increaseLocalCounterForValidatorAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); - expect(tx).not.to.emit(slashContract, 'UnavailabilitySlashed'); + await expect(tx).not.to.emit(slashContract, 'UnavailabilitySlashed'); }); it('Should sync with validator set for felony (slash tier-2)', async () => { @@ -183,19 +252,25 @@ describe('Slash indicator test', () => { const slasherIdx = 0; const slasheeIdx = 4; - await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); + await network.provider.send('hardhat_setCoinbase', [validatorCandidates[slasherIdx].address]); + + let _period = await localEpochController.currentPeriod(); for (let i = 0; i < felonyThreshold; i++) { - tx = await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); + tx = await slashContract + .connect(validatorCandidates[slasherIdx]) + .slash(validatorCandidates[slasheeIdx].address); if (i == misdemeanorThreshold - 1) { - expect(tx) + await expect(tx) .to.emit(slashContract, 'UnavailabilitySlashed') - .withArgs(coinbases[1].address, SlashType.MISDEMEANOR); + .withArgs(validatorCandidates[slasheeIdx].address, SlashType.MISDEMEANOR, _period); } } - expect(tx).to.emit(slashContract, 'UnavailabilitySlashed').withArgs(coinbases[1].address, SlashType.FELONY); + await expect(tx) + .to.emit(slashContract, 'UnavailabilitySlashed') + .withArgs(validatorCandidates[slasheeIdx].address, SlashType.FELONY, _period); setLocalCounterForValidatorAt(slasheeIdx, felonyThreshold); await validateIndicatorAt(slasheeIdx); }); @@ -204,13 +279,15 @@ describe('Slash indicator test', () => { let tx; const slasherIdx = 1; const slasheeIdx = 4; - await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); + await network.provider.send('hardhat_setCoinbase', [validatorCandidates[slasherIdx].address]); - tx = await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); + tx = await slashContract + .connect(validatorCandidates[slasherIdx]) + .slash(validatorCandidates[slasheeIdx].address); increaseLocalCounterForValidatorAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); - expect(tx).not.to.emit(slashContract, 'UnavailabilitySlashed'); + await expect(tx).not.to.emit(slashContract, 'UnavailabilitySlashed'); }); }); @@ -219,16 +296,16 @@ describe('Slash indicator test', () => { const slasherIdx = 0; const slasheeIdx = 5; let numberOfSlashing = felonyThreshold - 1; - await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); + await network.provider.send('hardhat_setCoinbase', [validatorCandidates[slasherIdx].address]); for (let i = 0; i < numberOfSlashing; i++) { - await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdx].address); + await slashContract.connect(validatorCandidates[slasherIdx]).slash(validatorCandidates[slasheeIdx].address); } setLocalCounterForValidatorAt(slasheeIdx, numberOfSlashing); await validateIndicatorAt(slasheeIdx); - await mockValidatorsContract.endPeriod(); + await localEpochController.mineToBeginOfNewPeriod(); resetLocalCounterForValidatorAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); @@ -238,11 +315,13 @@ describe('Slash indicator test', () => { const slasherIdx = 0; const slasheeIdxs = [6, 7, 8, 9, 10]; let numberOfSlashing = felonyThreshold - 1; - await network.provider.send('hardhat_setCoinbase', [coinbases[slasherIdx].address]); + await network.provider.send('hardhat_setCoinbase', [validatorCandidates[slasherIdx].address]); for (let i = 0; i < numberOfSlashing; i++) { for (let j = 0; j < slasheeIdxs.length; j++) { - await slashContract.connect(coinbases[slasherIdx]).slash(coinbases[slasheeIdxs[j]].address); + await slashContract + .connect(validatorCandidates[slasherIdx]) + .slash(validatorCandidates[slasheeIdxs[j]].address); } } @@ -251,7 +330,7 @@ describe('Slash indicator test', () => { await validateIndicatorAt(slasheeIdxs[j]); } - await mockValidatorsContract.endPeriod(); + await localEpochController.mineToBeginOfNewPeriod(); for (let j = 0; j < slasheeIdxs.length; j++) { resetLocalCounterForValidatorAt(slasheeIdxs[j]); @@ -259,5 +338,66 @@ describe('Slash indicator test', () => { } }); }); + + describe('Double signing slash', async () => { + let header1: BlockHeaderStruct; + let header2: BlockHeaderStruct; + + before(async () => { + await network.provider.send('hardhat_mine', [doubleSigningConstrainBlocks.toHexString()]); + }); + + it('Should not be able to slash themselves', async () => { + const slasherIdx = 0; + await network.provider.send('hardhat_setCoinbase', [validatorCandidates[slasherIdx].address]); + + let nextBlockHeight = await network.provider.send('eth_blockNumber'); + + header1 = generateDefaultBlockHeader(nextBlockHeight - 1); + header2 = generateDefaultBlockHeader(nextBlockHeight - 1); + + let tx = await slashContract + .connect(validatorCandidates[slasherIdx]) + .slashDoubleSign(validatorCandidates[slasherIdx].address, header1, header2); + + await expect(tx).to.not.emit(slashContract, 'UnavailabilitySlashed'); + }); + + it('Should not be able to slash with mismatched parent hash', async () => { + const slasherIdx = 0; + const slasheeIdx = 1; + let nextBlockHeight = await network.provider.send('eth_blockNumber'); + + header1 = generateDefaultBlockHeader(nextBlockHeight - 1); + header2 = generateDefaultBlockHeader(nextBlockHeight - 1); + + header1.parentHash = ethers.constants.HashZero.slice(0, -1) + '1'; + + let tx = await slashContract + .connect(validatorCandidates[slasherIdx]) + .slashDoubleSign(validatorCandidates[slasheeIdx].address, header1, header2); + + await expect(tx).to.not.emit(slashContract, 'UnavailabilitySlashed'); + }); + + it('Should be able to slash validator with double signing', async () => { + const slasherIdx = 0; + const slasheeIdx = 1; + let nextBlockHeight = await network.provider.send('eth_blockNumber'); + + header1 = generateDefaultBlockHeader(nextBlockHeight - 1); + header2 = generateDefaultBlockHeader(nextBlockHeight - 1); + + let tx = await slashContract + .connect(validatorCandidates[slasherIdx]) + .slashDoubleSign(validatorCandidates[slasheeIdx].address, header1, header2); + + let _period = await localEpochController.currentPeriod(); + + await expect(tx) + .to.emit(slashContract, 'UnavailabilitySlashed') + .withArgs(validatorCandidates[slasheeIdx].address, SlashType.DOUBLE_SIGNING, _period); + }); + }); }); }); diff --git a/test/validator/RoninValidatorSet-ArrangeValidators.test.ts b/test/validator/RoninValidatorSet-ArrangeValidators.test.ts index b66eaaf83..4b45623a1 100644 --- a/test/validator/RoninValidatorSet-ArrangeValidators.test.ts +++ b/test/validator/RoninValidatorSet-ArrangeValidators.test.ts @@ -7,8 +7,8 @@ import { Address } from 'hardhat-deploy/dist/types'; import * as RoninValidatorSet from '../helpers/ronin-validator-set'; import { Staking, - MockRoninValidatorSetExtends, - MockRoninValidatorSetExtends__factory, + MockRoninValidatorSetExtended, + MockRoninValidatorSetExtended__factory, Staking__factory, TransparentUpgradeableProxyV2__factory, MockSlashIndicator, @@ -17,7 +17,7 @@ import { Maintenance__factory, } from '../../src/types'; -let validatorContract: MockRoninValidatorSetExtends; +let validatorContract: MockRoninValidatorSetExtended; let stakingContract: Staking; let slashIndicator: MockSlashIndicator; @@ -107,7 +107,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { /// Deploy validator mock contract /// - const validatorLogicContract = await new MockRoninValidatorSetExtends__factory(deployer).deploy(); + const validatorLogicContract = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); await validatorLogicContract.deployed(); const validatorProxyContract = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( @@ -126,7 +126,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { ]) ); await validatorProxyContract.deployed(); - validatorContract = MockRoninValidatorSetExtends__factory.connect(validatorProxyContract.address, deployer); + validatorContract = MockRoninValidatorSetExtended__factory.connect(validatorProxyContract.address, deployer); /// /// Deploy staking contract diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index 454403c8f..25088c0a4 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -5,8 +5,8 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { Staking, - MockRoninValidatorSetExtends, - MockRoninValidatorSetExtends__factory, + MockRoninValidatorSetExtended, + MockRoninValidatorSetExtended__factory, Staking__factory, TransparentUpgradeableProxyV2__factory, MockSlashIndicator, @@ -17,7 +17,7 @@ import { import * as RoninValidatorSet from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../helpers/utils'; -let roninValidatorSet: MockRoninValidatorSetExtends; +let roninValidatorSet: MockRoninValidatorSetExtended; let stakingContract: Staking; let slashIndicator: MockSlashIndicator; @@ -81,7 +81,7 @@ describe('Ronin Validator Set test', () => { /// Deploy validator mock contract /// - const validatorLogicContract = await new MockRoninValidatorSetExtends__factory(deployer).deploy(); + const validatorLogicContract = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); await validatorLogicContract.deployed(); const validatorProxyContract = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( @@ -100,7 +100,7 @@ describe('Ronin Validator Set test', () => { ]) ); await validatorProxyContract.deployed(); - roninValidatorSet = MockRoninValidatorSetExtends__factory.connect(validatorProxyContract.address, deployer); + roninValidatorSet = MockRoninValidatorSetExtended__factory.connect(validatorProxyContract.address, deployer); /// /// Deploy staking contract From e40d6e5a2fdd4bab9b95f4f6af9fb1c87268d721 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 28 Sep 2022 15:43:25 +0700 Subject: [PATCH 165/190] [Validator] Fix epoch index & order of deleting (#21) Fix minor --- contracts/validator/RoninValidatorSet.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/validator/RoninValidatorSet.sol b/contracts/validator/RoninValidatorSet.sol index e9b87c56e..3f068045c 100644 --- a/contracts/validator/RoninValidatorSet.sol +++ b/contracts/validator/RoninValidatorSet.sol @@ -256,14 +256,14 @@ contract RoninValidatorSet is * @inheritdoc IRoninValidatorSet */ function epochOf(uint256 _block) public view virtual override returns (uint256) { - return _block / _numberOfBlocksInEpoch + 1; + return _block == 0 ? 0 : _block / _numberOfBlocksInEpoch + 1; } /** * @inheritdoc IRoninValidatorSet */ function periodOf(uint256 _block) public view virtual override returns (uint256) { - return _block / (_numberOfBlocksInEpoch * _numberOfEpochsInPeriod) + 1; + return _block == 0 ? 0 : _block / (_numberOfBlocksInEpoch * _numberOfEpochsInPeriod) + 1; } /** @@ -418,8 +418,8 @@ contract RoninValidatorSet is } for (uint256 _i = _newValidatorCount; _i < validatorCount; _i++) { - delete _validator[_i]; delete _validatorMap[_validator[_i]]; + delete _validator[_i]; } uint256 _count; From 684682ad6e9410a89b400b8d5800344a22ff01a1 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Thu, 29 Sep 2022 13:59:57 +0700 Subject: [PATCH 166/190] Add renounce function (#20) - Add `requestRenounce` function: revoke candidates at the end of the next period. - Filter candidates which are insufficient balance at the end of each period. - Checks and removes the candidates revoked at the end of each epoch. Co-authored-by: Bao --- contracts/interfaces/ICandidateManager.sol | 42 ++- contracts/interfaces/IRewardPool.sol | 14 +- contracts/interfaces/IRoninValidatorSet.sol | 10 - contracts/interfaces/IStaking.sol | 62 +++- contracts/mocks/MockStaking.sol | 7 + contracts/mocks/MockValidatorSet.sol | 34 ++- contracts/staking/Staking.sol | 63 ++++- contracts/staking/StakingManager.sol | 165 ++++++----- contracts/validator/CandidateManager.sol | 80 ++++-- contracts/validator/RoninValidatorSet.sol | 47 ++-- test/helpers/ronin-validator-set.ts | 6 +- .../integration/ActionSlashValidators.test.ts | 19 +- test/integration/ActionSubmitReward.test.ts | 4 +- test/integration/ActionWrapUpEpoch.test.ts | 4 +- test/maintainance/Maintenance.test.ts | 2 +- test/slash/SlashIndicator.test.ts | 2 +- ...king.test.ts => RewardCalculation.test.ts} | 2 +- test/staking/Staking.test.ts | 265 +++++------------- test/validator/RoninValidatorSet.test.ts | 6 +- 19 files changed, 435 insertions(+), 399 deletions(-) rename test/staking/{CoreStaking.test.ts => RewardCalculation.test.ts} (99%) diff --git a/contracts/interfaces/ICandidateManager.sol b/contracts/interfaces/ICandidateManager.sol index 5b840c635..482e082f0 100644 --- a/contracts/interfaces/ICandidateManager.sol +++ b/contracts/interfaces/ICandidateManager.sol @@ -13,20 +13,20 @@ interface ICandidateManager { // The percentile of reward that validators can be received, the rest goes to the delegators. // Values in range [0; 100_00] stands for 0-100% uint256 commissionRate; + // The block that the candidate to be revoked. + uint256 revokedBlock; // Extra data bytes extraData; } /// @dev Emitted when the maximum number of validator candidates is updated. event MaxValidatorCandidateUpdated(uint256 threshold); - /// @dev Emitted when the validator candidate is added. - event ValidatorCandidateAdded( - address indexed consensusAddr, - address indexed treasuryAddr, - uint256 indexed candidateIdx - ); - /// @dev Emitted when the validator candidate is removed. - event ValidatorCandidateRemoved(address indexed consensusAddr); + /// @dev Emitted when the validator candidate is granted. + event CandidateGranted(address indexed consensusAddr, address indexed treasuryAddr, address indexed admin); + /// @dev Emitted when the revoked block of a candidate is updated. + event CandidateRevokedBlockUpdated(address indexed consensusAddr, uint256 revokedBlock); + /// @dev Emitted when the validator candidate is revoked. + event CandidatesRevoked(address[] consensusAddrs); /** * @dev Returns the maximum number of validator candidate. @@ -45,15 +45,15 @@ interface ICandidateManager { function setMaxValidatorCandidate(uint256) external; /** - * @dev Adds a validator candidate. + * @dev Grants a validator candidate. * * Requirements: * - The method caller is staking contract. * - * Emits the event `ValidatorCandidateAdded`. + * Emits the event `CandidateGranted`. * */ - function addValidatorCandidate( + function grantValidatorCandidate( address _admin, address _consensusAddr, address payable _treasuryAddr, @@ -61,13 +61,15 @@ interface ICandidateManager { ) external; /** - * @dev Syncs the validator candidate list (removes the ones who have insufficient minimum candidate balance). - * Returns the total balance list of the new candidate list. + * @dev Requests to revoke a validator candidate. * - * Emits the event `ValidatorCandidateRemoved` when a candidate is removed. + * Requirements: + * - The method caller is staking contract. + * + * Emits the event `CandidateRevokedBlockUpdated`. * */ - function syncCandidates() external returns (uint256[] memory _balances); + function requestRevokeCandidate(address) external; /** * @dev Returns whether the address is a validator (candidate). @@ -88,4 +90,14 @@ interface ICandidateManager { * @dev Returns whether the address is the candidate admin. */ function isCandidateAdmin(address _candidate, address _admin) external view returns (bool); + + /** + * @dev Returns the number of epochs in a period. + */ + function numberOfEpochsInPeriod() external view returns (uint256 _numberOfEpochs); + + /** + * @dev Returns the number of blocks in a epoch. + */ + function numberOfBlocksInEpoch() external view returns (uint256 _numberOfBlocks); } diff --git a/contracts/interfaces/IRewardPool.sol b/contracts/interfaces/IRewardPool.sol index 314a890fb..d0b68e3c5 100644 --- a/contracts/interfaces/IRewardPool.sol +++ b/contracts/interfaces/IRewardPool.sol @@ -69,17 +69,25 @@ interface IRewardPool { function getPendingReward(address _poolAddr, address _user) external view returns (uint256 _amount); /** - * @dev Returns the staking amount of the user. + * @dev Returns the staked amount of the user. */ function balanceOf(address _poolAddr, address _user) external view returns (uint256); /** - * @dev Returns the total staking amount of all users. + * @dev Returns the staked amounts of the users. + */ + function bulkBalanceOf(address[] calldata _poolAddrs, address[] calldata _userList) + external + view + returns (uint256[] memory); + + /** + * @dev Returns the total staked amount of all users. */ function totalBalance(address _poolAddr) external view returns (uint256); /** - * @dev Returns the total staking amount of all users. + * @dev Returns the total staked amount of all users. */ function totalBalances(address[] calldata _poolAddr) external view returns (uint256[] memory); } diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index 5c2e092c4..a292b765a 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -97,16 +97,6 @@ interface IRoninValidatorSet is ICandidateManager { // FUNCTIONS FOR NORMAL USER // /////////////////////////////////////////////////////////////////////////////////////// - /** - * @dev Returns the number of epochs in a period. - */ - function numberOfEpochsInPeriod() external view returns (uint256 _numberOfEpochs); - - /** - * @dev Returns the number of blocks in a epoch. - */ - function numberOfBlocksInEpoch() external view returns (uint256 _numberOfBlocks); - /** * @dev Returns the maximum number of validators in the epoch */ diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 38ae5559c..0addf5505 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -18,11 +18,12 @@ interface IStaking is IRewardPool { mapping(address => uint256) delegatedAmount; } - event ValidatorPoolAdded(address indexed validator, address indexed admin); - /// @dev Emitted when the validator candidate requested to renounce. - event ValidatorRenounceRequested(address indexed consensusAddr, uint256 amount); - /// @dev Emitted when the renounce request is finalized. - event ValidatorRenounceFinalized(address indexed consensusAddr, uint256 amount); + /// @dev Emitted when the validator pool is approved. + event PoolApproved(address indexed validator, address indexed admin); + /// @dev Emitted when the validator pool is deprecated. + event PoolsDeprecated(address[] validator); + /// @dev Emitted when the staked amount is deprecated. + event StakedAmountDeprecated(address indexed validator, address indexed admin, uint256 amount); /// @dev Emitted when the pool admin staked for themself. event Staked(address indexed validator, uint256 amount); /// @dev Emitted when the pool admin unstaked the amount of RON from themself. @@ -94,15 +95,27 @@ interface IStaking is IRewardPool { function sinkPendingReward(address _consensusAddr) external; /** - * @dev Deducts from staking amount of the validator `_consensusAddr` for `_amount`. + * @dev Deducts from staked amount of the validator `_consensusAddr` for `_amount`. * * Requirements: * - The method caller is validator contract. * - * Emits the event `Unstaked` and `Undelegated` event. + * Emits the event `Unstaked`. * */ - function deductStakingAmount(address _consensusAddr, uint256 _amount) external; + function deductStakedAmount(address _consensusAddr, uint256 _amount) external; + + /** + * @dev Deprecates the pool. + * + * Requirements: + * - The method caller is validator contract. + * + * Emits the event `PoolsDeprecated` and `Unstaked` events. + * Emits the event `StakedAmountDeprecated` if the contract cannot transfer RON back to the pool admin. + * + */ + function deprecatePools(address[] calldata _pools) external; /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR VALIDATOR CANDIDATE // @@ -116,13 +129,13 @@ interface IStaking is IRewardPool { * - The treasury is able to receive RON. * - The amount is larger than or equal to the minimum validator balance `minValidatorBalance()`. * - * Emits the event `ValidatorPoolAdded`. + * Emits the event `PoolApproved`. * * @param _candidateAdmin the candidate admin will be stored in the validator contract, used for calling function that affects * to its candidate. IE: scheduling maintenance. * */ - function proposeValidator( + function applyValidatorCandidate( address _candidateAdmin, address _consensusAddr, address payable _treasuryAddr, @@ -137,7 +150,7 @@ interface IStaking is IRewardPool { * - The method caller is the pool admin. * - The `msg.value` is larger than 0. * - * Emits the `Staked` event and the `Delegated` event. + * Emits the event `Staked`. * */ function stake(address _consensusAddr) external payable; @@ -149,7 +162,7 @@ interface IStaking is IRewardPool { * - The consensus address is a validator candidate. * - The method caller is the pool admin. * - * Emits the `Unstaked` event and the `Undelegated` event. + * Emits the event `Unstaked`. * */ function unstake(address _consensusAddr, uint256 _amount) external; @@ -162,7 +175,7 @@ interface IStaking is IRewardPool { * - The method caller is the pool admin. * */ - function renounce(address consensusAddr) external; + function requestRenounce(address consensusAddr) external; /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR DELEGATOR // @@ -191,6 +204,17 @@ interface IStaking is IRewardPool { */ function undelegate(address _consensusAddr, uint256 _amount) external; + /** + * @dev Bulk unstakes from a list of candidates. + * + * Requirements: + * - The method caller is not the pool admin. + * + * Emits the events `Undelegated`. + * + */ + function bulkUndelegate(address[] calldata _consensusAddrs, uint256[] calldata _amounts) external; + /** * @dev Unstakes an amount of RON from the `_consensusAddrSrc` and stake for `_consensusAddrDst`. * @@ -236,4 +260,16 @@ interface IStaking is IRewardPool { function delegateRewards(address[] calldata _consensusAddrList, address _consensusAddrDst) external returns (uint256 _amount); + + /** + * @dev Returns the staking pool detail. + */ + function getStakingPool(address) + external + view + returns ( + address _admin, + uint256 _stakedAmount, + uint256 _totalBalance + ); } diff --git a/contracts/mocks/MockStaking.sol b/contracts/mocks/MockStaking.sol index 54c51fef9..bdd9a7612 100644 --- a/contracts/mocks/MockStaking.sol +++ b/contracts/mocks/MockStaking.sol @@ -69,6 +69,13 @@ contract MockStaking is RewardCalculation { return _stakingBalance[_user]; } + function bulkBalanceOf(address[] calldata _poolAddrs, address[] calldata _userList) + external + view + override + returns (uint256[] memory) + {} + function totalBalance(address) public view virtual override returns (uint256) { return _totalBalance; } diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index ac528f8d3..c52a026ce 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -11,8 +11,8 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { address public stakingVestingContract; address public slashIndicatorContract; - uint256 public numberOfEpochsInPeriod; - uint256 public numberOfBlocksInEpoch; + uint256 internal _numberOfEpochsInPeriod; + uint256 internal _numberOfBlocksInEpoch; /// @dev Mapping from period number => slashed mapping(uint256 => bool) internal _periodSlashed; uint256[] internal _periods; @@ -22,15 +22,15 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { address _slashIndicatorContract, address _stakingVestingContract, uint256 __maxValidatorCandidate, - uint256 _numberOfEpochsInPeriod, - uint256 _numberOfBlocksInEpoch + uint256 __numberOfEpochsInPeriod, + uint256 __numberOfBlocksInEpoch ) { _setStakingContract(__stakingContract); _setMaxValidatorCandidate(__maxValidatorCandidate); slashIndicatorContract = _slashIndicatorContract; stakingVestingContract = _stakingVestingContract; - numberOfEpochsInPeriod = _numberOfEpochsInPeriod; - numberOfBlocksInEpoch = _numberOfBlocksInEpoch; + _numberOfEpochsInPeriod = __numberOfEpochsInPeriod; + _numberOfBlocksInEpoch = __numberOfBlocksInEpoch; } function depositReward() external payable { @@ -47,7 +47,7 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { function slashFelony(address _validator) external { _stakingContract.sinkPendingReward(_validator); - _stakingContract.deductStakingAmount(_validator, 1); + _stakingContract.deductStakedAmount(_validator, 1); } function slashDoubleSign(address _validator) external { @@ -68,7 +68,9 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { function submitBlockReward() external payable override {} - function wrapUpEpoch() external payable override {} + function wrapUpEpoch() external payable override { + _filterUnsatisfiedCandidates(0); + } function getLastUpdatedBlock() external view override returns (uint256) {} @@ -92,9 +94,9 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { function setMaxValidatorNumber(uint256 _maxValidatorNumber) external override {} - function setNumberOfBlocksInEpoch(uint256 _numberOfBlocksInEpoch) external override {} + function setNumberOfBlocksInEpoch(uint256 _number) external override {} - function setNumberOfEpochsInPeriod(uint256 _numberOfEpochsInPeriod) external override {} + function setNumberOfEpochsInPeriod(uint256 _number) external override {} function maxValidatorNumber() external view override returns (uint256 _maximumValidatorNumber) {} @@ -112,9 +114,15 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { function getPriorityStatus(address _addr) external view override returns (bool) {} - function isValidator( - address /* _addr */ - ) external pure override returns (bool) { + function isValidator(address) external pure override returns (bool) { return true; } + + function numberOfEpochsInPeriod() public view override(CandidateManager, ICandidateManager) returns (uint256) { + return _numberOfEpochsInPeriod; + } + + function numberOfBlocksInEpoch() public view override(CandidateManager, ICandidateManager) returns (uint256) { + return _numberOfBlocksInEpoch; + } } diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index cc41f6bd7..0980a3ba6 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -30,9 +30,22 @@ contract Staking is IStaking, StakingManager, Initializable { _setMinValidatorBalance(__minValidatorBalance); } - /////////////////////////////////////////////////////////////////////////////////////// - // FUNCTIONS FOR GOVERNANCE // - /////////////////////////////////////////////////////////////////////////////////////// + /** + * @inheritdoc IStaking + */ + function getStakingPool(address _poolAddr) + external + view + poolExists(_poolAddr) + returns ( + address _admin, + uint256 _stakedAmount, + uint256 _totalBalance + ) + { + PoolDetail storage _pool = _stakingPool[_poolAddr]; + return (_pool.admin, _pool.stakedAmount, _pool.totalBalance); + } /** * @inheritdoc IStaking @@ -81,9 +94,33 @@ contract Staking is IStaking, StakingManager, Initializable { /** * @inheritdoc IStaking */ - function deductStakingAmount(address _consensusAddr, uint256 _amount) external onlyValidatorContract { - PoolDetail storage _pool = _stakingPool[_consensusAddr]; - _unstake(_pool, _pool.admin, _amount); + function deductStakedAmount(address _consensusAddr, uint256 _amount) public onlyValidatorContract { + return _deductStakedAmount(_stakingPool[_consensusAddr], _amount); + } + + /** + * @inheritdoc IStaking + */ + function deprecatePools(address[] calldata _pools) external override onlyValidatorContract { + if (_pools.length == 0) { + return; + } + + uint256 _amount; + for (uint _i = 0; _i < _pools.length; _i++) { + PoolDetail storage _pool = _stakingPool[_pools[_i]]; + _amount = _pool.stakedAmount; + _deductStakedAmount(_pool, _pool.stakedAmount); + if (_amount > 0) { + if (!_sendRON(payable(_pool.admin), _amount)) { + emit StakedAmountDeprecated(_pool.addr, _pool.admin, _amount); + } + } + + delete _stakingPool[_pool.addr]; + } + + emit PoolsDeprecated(_pools); } /////////////////////////////////////////////////////////////////////////////////////// @@ -114,4 +151,18 @@ contract Staking is IStaking, StakingManager, Initializable { function _periodOf(uint256 _block) internal view virtual override returns (uint256) { return IRoninValidatorSet(_validatorContract).periodOf(_block); } + + /** + * @dev Deducts from staked amount of the validator `_consensusAddr` for `_amount`. + * + * Emits the event `Unstaked`. + * + */ + function _deductStakedAmount(PoolDetail storage _pool, uint256 _amount) internal { + _amount = Math.min(_pool.stakedAmount, _amount); + + _pool.stakedAmount -= _amount; + _changeDelegatedAmount(_pool, _pool.admin, _pool.stakedAmount, _pool.totalBalance - _amount); + emit Unstaked(_pool.addr, _amount); + } } diff --git a/contracts/staking/StakingManager.sol b/contracts/staking/StakingManager.sol index 3c977dcd4..18d6d1bed 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/staking/StakingManager.sol @@ -6,6 +6,7 @@ import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "../extensions/RONTransferHelper.sol"; import "../extensions/HasValidatorContract.sol"; import "../interfaces/IStaking.sol"; +import "../libraries/Math.sol"; import "./RewardCalculation.sol"; abstract contract StakingManager is @@ -28,11 +29,13 @@ abstract contract StakingManager is _; } - modifier onlyValidatorCandidate(address _poolAddr) { - require( - _validatorContract.isValidatorCandidate(_poolAddr), - "StakingManager: method caller must not be the pool admin" - ); + modifier onlyPoolAdmin(PoolDetail storage _pool, address _requester) { + require(_pool.admin == _requester, "StakingManager: requester must be the pool admin"); + _; + } + + modifier poolExists(address _poolAddr) { + require(_validatorContract.isValidatorCandidate(_poolAddr), "StakingManager: query for non-existent pool"); _; } @@ -48,6 +51,22 @@ abstract contract StakingManager is return _stakingPool[_poolAddr].delegatedAmount[_user]; } + /** + * @inheritdoc IRewardPool + */ + function bulkBalanceOf(address[] calldata _poolAddrs, address[] calldata _userList) + external + view + override + returns (uint256[] memory _balances) + { + require(_poolAddrs.length > 0 && _poolAddrs.length == _userList.length, "StakingManager: invalid input array"); + _balances = new uint256[](_poolAddrs.length); + for (uint _i = 0; _i < _balances.length; _i++) { + _balances[_i] = _stakingPool[_poolAddrs[_i]].delegatedAmount[_userList[_i]]; + } + } + /** * @inheritdoc IRewardPool */ @@ -77,7 +96,7 @@ abstract contract StakingManager is /** * @inheritdoc IStaking */ - function proposeValidator( + function applyValidatorCandidate( address _candidateAdmin, address _consensusAddr, address payable _treasuryAddr, @@ -85,31 +104,27 @@ abstract contract StakingManager is ) external payable override nonReentrant { uint256 _amount = msg.value; address payable _poolAdmin = payable(msg.sender); - _proposeValidator(_poolAdmin, _candidateAdmin, _consensusAddr, _treasuryAddr, _commissionRate, _amount); + _applyValidatorCandidate(_poolAdmin, _candidateAdmin, _consensusAddr, _treasuryAddr, _commissionRate, _amount); PoolDetail storage _pool = _stakingPool[_consensusAddr]; _pool.admin = _poolAdmin; _pool.addr = _consensusAddr; _stake(_stakingPool[_consensusAddr], _poolAdmin, _amount); - emit ValidatorPoolAdded(_consensusAddr, _poolAdmin); + emit PoolApproved(_consensusAddr, _poolAdmin); } /** * @inheritdoc IStaking */ - function stake(address _consensusAddr) external payable override noEmptyValue onlyValidatorCandidate(_consensusAddr) { + function stake(address _consensusAddr) external payable override noEmptyValue poolExists(_consensusAddr) { _stake(_stakingPool[_consensusAddr], msg.sender, msg.value); } /** * @inheritdoc IStaking */ - function unstake(address _consensusAddr, uint256 _amount) - external - override - nonReentrant - onlyValidatorCandidate(_consensusAddr) - { + function unstake(address _consensusAddr, uint256 _amount) external override nonReentrant poolExists(_consensusAddr) { + require(_amount > 0, "StakingManager: invalid amount"); address _delegator = msg.sender; PoolDetail storage _pool = _stakingPool[_consensusAddr]; uint256 _remainAmount = _pool.stakedAmount - _amount; @@ -122,9 +137,12 @@ abstract contract StakingManager is /** * @inheritdoc IStaking */ - function renounce(address _consensusAddr) external onlyValidatorCandidate(_consensusAddr) { - // TODO(Thor): implement this function - revert("unimplemented"); + function requestRenounce(address _consensusAddr) + external + poolExists(_consensusAddr) + onlyPoolAdmin(_stakingPool[_consensusAddr], msg.sender) + { + _validatorContract.requestRevokeCandidate(_consensusAddr); } /** @@ -139,7 +157,7 @@ abstract contract StakingManager is * to its candidate. IE: scheduling maintenance. * */ - function _proposeValidator( + function _applyValidatorCandidate( address payable _poolAdmin, address _candidateAdmin, address _consensusAddr, @@ -151,14 +169,14 @@ abstract contract StakingManager is require(_sendRON(_treasuryAddr, 0), "StakingManager: treasury cannot receive RON"); require(_amount >= minValidatorBalance(), "StakingManager: insufficient amount"); - _validatorContract.addValidatorCandidate(_candidateAdmin, _consensusAddr, _treasuryAddr, _commissionRate); + _validatorContract.grantValidatorCandidate(_candidateAdmin, _consensusAddr, _treasuryAddr, _commissionRate); } /** * @dev Stakes for the validator candidate. * * Requirements: - * - The user address is equal the candidate staking address. + * - The address `_requester` must be the pool admin. * * Emits the `Staked` event. * @@ -167,12 +185,10 @@ abstract contract StakingManager is PoolDetail storage _pool, address _requester, uint256 _amount - ) internal { - require(_pool.admin == _requester, "StakingManager: requester must be the pool admin"); + ) internal onlyPoolAdmin(_pool, _requester) { _pool.stakedAmount += _amount; + _changeDelegatedAmount(_pool, _requester, _pool.stakedAmount, _pool.totalBalance + _amount); emit Staked(_pool.addr, _amount); - - _unsafeDelegate(_pool, _requester, _amount); } /** @@ -180,7 +196,6 @@ abstract contract StakingManager is * * Requirements: * - The address `_requester` must be the pool admin. - * - The remain balance must be greater than the minimum validator candidate thresold `minValidatorBalance()`. * * Emits the `Unstaked` event. * @@ -189,13 +204,12 @@ abstract contract StakingManager is PoolDetail storage _pool, address _requester, uint256 _amount - ) internal { - require(_pool.admin == _requester, "StakingManager: requester must be the pool admin"); + ) internal onlyPoolAdmin(_pool, _requester) { require(_amount <= _pool.stakedAmount, "StakingManager: insufficient staked amount"); _pool.stakedAmount -= _amount; + _changeDelegatedAmount(_pool, _requester, _pool.stakedAmount, _pool.totalBalance - _amount); emit Unstaked(_pool.addr, _amount); - _unsafeUndelegate(_pool, _requester, _amount); } /////////////////////////////////////////////////////////////////////////////////////// @@ -205,7 +219,7 @@ abstract contract StakingManager is /** * @inheritdoc IStaking */ - function delegate(address _consensusAddr) external payable noEmptyValue onlyValidatorCandidate(_consensusAddr) { + function delegate(address _consensusAddr) external payable noEmptyValue poolExists(_consensusAddr) { _delegate(_stakingPool[_consensusAddr], msg.sender, msg.value); } @@ -213,12 +227,31 @@ abstract contract StakingManager is * @inheritdoc IStaking */ function undelegate(address _consensusAddr, uint256 _amount) external nonReentrant { - // TODO: add bulk function to undelegate a list of consensus addresses address payable _delegator = payable(msg.sender); _undelegate(_stakingPool[_consensusAddr], _delegator, _amount); require(_sendRON(_delegator, _amount), "StakingManager: could not transfer RON"); } + /** + * @inheritdoc IStaking + */ + function bulkUndelegate(address[] calldata _consensusAddrs, uint256[] calldata _amounts) external nonReentrant { + require( + _consensusAddrs.length > 0 && _consensusAddrs.length == _amounts.length, + "StakingManager: invalid array length" + ); + + address payable _delegator = payable(msg.sender); + uint256 _total; + + for (uint _i = 0; _i < _consensusAddrs.length; _i++) { + _total += _amounts[_i]; + _undelegate(_stakingPool[_consensusAddrs[_i]], _delegator, _amounts[_i]); + } + + require(_sendRON(_delegator, _total), "StakingManager: could not transfer RON"); + } + /** * @inheritdoc IStaking */ @@ -226,7 +259,7 @@ abstract contract StakingManager is address _consensusAddrSrc, address _consensusAddrDst, uint256 _amount - ) external nonReentrant onlyValidatorCandidate(_consensusAddrDst) { + ) external nonReentrant poolExists(_consensusAddrDst) { address _delegator = msg.sender; _undelegate(_stakingPool[_consensusAddrSrc], _delegator, _amount); _delegate(_stakingPool[_consensusAddrDst], _delegator, _amount); @@ -266,7 +299,7 @@ abstract contract StakingManager is external override nonReentrant - onlyValidatorCandidate(_consensusAddrDst) + poolExists(_consensusAddrDst) returns (uint256 _amount) { return _delegateRewards(msg.sender, _consensusAddrList, _consensusAddrDst); @@ -275,43 +308,34 @@ abstract contract StakingManager is /** * @dev Delegates from a validator address. * + * Requirements: + * - The delegator is not the pool admin. + * * Emits the `Delegated` event. * * Note: This function does not verify the `msg.value` with the amount. * */ - function _unsafeDelegate( - PoolDetail storage _pool, - address _delegator, - uint256 _amount - ) internal { - uint256 _newBalance = _pool.delegatedAmount[_delegator] + _amount; - _syncUserReward(_pool.addr, _delegator, _newBalance); - - _pool.totalBalance += _amount; - _pool.delegatedAmount[_delegator] = _newBalance; - emit Delegated(_delegator, _pool.addr, _amount); - } - - /** - * @dev See `_unsafeDelegate`. - * - * Requirements: - * - The delegator is not the pool admin. - * - */ function _delegate( PoolDetail storage _pool, address _delegator, uint256 _amount ) internal notPoolAdmin(_pool, _delegator) { - _unsafeDelegate(_pool, _delegator, _amount); + _changeDelegatedAmount( + _pool, + _delegator, + _pool.delegatedAmount[_delegator] + _amount, + _pool.totalBalance + _amount + ); + emit Delegated(_delegator, _pool.addr, _amount); } /** * @dev Undelegates from a validator address. * * Requirements: + * - The delegator is not the pool admin. + * - The amount is larger than 0. * - The delegated amount is larger than or equal to the undelegated amount. * * Emits the `Undelegated` event. @@ -319,33 +343,34 @@ abstract contract StakingManager is * Note: Consider transferring back the amount of RON after calling this function. * */ - function _unsafeUndelegate( + function _undelegate( PoolDetail storage _pool, address _delegator, uint256 _amount - ) private { + ) private notPoolAdmin(_pool, _delegator) { + require(_amount > 0, "StakingManager: invalid amount"); require(_pool.delegatedAmount[_delegator] >= _amount, "StakingManager: insufficient amount to undelegate"); - - uint256 _newBalance = _pool.delegatedAmount[_delegator] - _amount; - _syncUserReward(_pool.addr, _delegator, _newBalance); - _pool.totalBalance -= _amount; - _pool.delegatedAmount[_delegator] = _newBalance; + _changeDelegatedAmount( + _pool, + _delegator, + _pool.delegatedAmount[_delegator] - _amount, + _pool.totalBalance - _amount + ); emit Undelegated(_delegator, _pool.addr, _amount); } /** - * @dev See `_unsafeUndelegate`. - * - * Requirements: - * - The delegator is not the pool admin. - * + * @dev Changes the delelgate amount. */ - function _undelegate( + function _changeDelegatedAmount( PoolDetail storage _pool, address _delegator, - uint256 _amount - ) private notPoolAdmin(_pool, _delegator) { - _unsafeUndelegate(_pool, _delegator, _amount); + uint256 _newDelegateBalance, + uint256 _newTotalBalance + ) internal { + _syncUserReward(_pool.addr, _delegator, _newDelegateBalance); + _pool.totalBalance = _newTotalBalance; + _pool.delegatedAmount[_delegator] = _newDelegateBalance; } /** diff --git a/contracts/validator/CandidateManager.sol b/contracts/validator/CandidateManager.sol index 9200351ce..8aa6593bc 100644 --- a/contracts/validator/CandidateManager.sol +++ b/contracts/validator/CandidateManager.sol @@ -7,7 +7,7 @@ import "../interfaces/ICandidateManager.sol"; import "../interfaces/IStaking.sol"; import "../libraries/Sorting.sol"; -contract CandidateManager is ICandidateManager, HasStakingContract { +abstract contract CandidateManager is ICandidateManager, HasStakingContract { /// @dev Maximum number of validator candidate uint256 private _maxValidatorCandidate; @@ -35,7 +35,7 @@ contract CandidateManager is ICandidateManager, HasStakingContract { /** * @inheritdoc ICandidateManager */ - function addValidatorCandidate( + function grantValidatorCandidate( address _admin, address _consensusAddr, address payable _treasuryAddr, @@ -52,33 +52,22 @@ contract CandidateManager is ICandidateManager, HasStakingContract { _consensusAddr, _treasuryAddr, _commissionRate, + type(uint256).max, new bytes(0) ); - emit ValidatorCandidateAdded(_consensusAddr, _treasuryAddr, _candidateIndex[_consensusAddr]); + emit CandidateGranted(_consensusAddr, _treasuryAddr, _admin); } /** * @inheritdoc ICandidateManager */ - function syncCandidates() public override returns (uint256[] memory _balances) { - // This is a temporary approach since the slashing issue is still not finalized. - // Consider calling validator contract to renounce for the removed candidates. - // Read more about slashing issue at: https://www.notion.so/skymavis/Slashing-Issue-9610ae1452434faca1213ab2e1d7d944 - IStaking _staking = _stakingContract; - uint256 _minBalance = _staking.minValidatorBalance(); - _balances = _staking.totalBalances(_candidates); - - uint256 _length = _candidates.length; - for (uint _i = 0; _i < _length; _i++) { - if (_balances[_i] < _minBalance) { - _balances[_i] = _balances[--_length]; - _removeCandidate(_candidates[_i]); - } - } - - assembly { - mstore(_balances, _length) - } + function requestRevokeCandidate(address _consensusAddr) external override onlyStakingContract { + require(isValidatorCandidate(_consensusAddr), "CandidateManager: query for non-existent candidate"); + uint256 _blockLength = numberOfBlocksInEpoch() * numberOfEpochsInPeriod(); + uint256 _revokedBlock = (block.number / _blockLength) * _blockLength + _blockLength * 2 - 1; + require(_revokedBlock < _candidateInfo[_consensusAddr].revokedBlock, "CandidateManager: invalid block number"); + _candidateInfo[_consensusAddr].revokedBlock = _revokedBlock; + emit CandidateRevokedBlockUpdated(_consensusAddr, _revokedBlock); } /** @@ -105,6 +94,53 @@ contract CandidateManager is ICandidateManager, HasStakingContract { return _candidates; } + /** + * @inheritdoc ICandidateManager + */ + function numberOfEpochsInPeriod() public view virtual returns (uint256); + + /** + * @inheritdoc ICandidateManager + */ + function numberOfBlocksInEpoch() public view virtual returns (uint256); + + /** + * @dev Removes unsastisfied candidates (the ones who have insufficient minimum candidate balance). + * Returns the total balance list of the new candidate list. + * + * Emits the event `CandidatesRevoked` when a candidate is revoked. + * + */ + function _filterUnsatisfiedCandidates(uint256 _minBalance) internal returns (uint256[] memory _balances) { + IStaking _staking = _stakingContract; + _balances = _staking.totalBalances(_candidates); + + uint256 _length = _candidates.length; + address[] memory _unsatisfiedCandidates = new address[](_length); + uint256 _unsatisfiedCount; + address _addr; + for (uint _i = 0; _i < _length; _i++) { + _addr = _candidates[_i]; + if (_balances[_i] < _minBalance || _candidateInfo[_addr].revokedBlock <= block.number) { + _balances[_i] = _balances[--_length]; + _unsatisfiedCandidates[_unsatisfiedCount++] = _addr; + _removeCandidate(_addr); + } + } + + if (_unsatisfiedCount > 0) { + assembly { + mstore(_unsatisfiedCandidates, _unsatisfiedCount) + } + emit CandidatesRevoked(_unsatisfiedCandidates); + _staking.deprecatePools(_unsatisfiedCandidates); + } + + assembly { + mstore(_balances, _length) + } + } + /** * @inheritdoc ICandidateManager */ diff --git a/contracts/validator/RoninValidatorSet.sol b/contracts/validator/RoninValidatorSet.sol index 3f068045c..5ce63f4e4 100644 --- a/contracts/validator/RoninValidatorSet.sol +++ b/contracts/validator/RoninValidatorSet.sol @@ -187,7 +187,7 @@ contract RoninValidatorSet is } } - _updateValidatorSet(); + _updateValidatorSet(_periodEnding); } /** @@ -219,7 +219,7 @@ contract RoninValidatorSet is } if (_slashAmount > 0) { - IStaking(_stakingContract).deductStakingAmount(_validatorAddr, _slashAmount); + IStaking(_stakingContract).deductStakedAmount(_validatorAddr, _slashAmount); } emit ValidatorPunished(_validatorAddr, _jailedUntil[_validatorAddr], _slashAmount); @@ -299,16 +299,26 @@ contract RoninValidatorSet is } /** - * @inheritdoc IRoninValidatorSet + * @inheritdoc ICandidateManager */ - function numberOfEpochsInPeriod() external view override returns (uint256 _numberOfEpochs) { + function numberOfEpochsInPeriod() + public + view + override(CandidateManager, ICandidateManager) + returns (uint256 _numberOfEpochs) + { return _numberOfEpochsInPeriod; } /** - * @inheritdoc IRoninValidatorSet + * @inheritdoc ICandidateManager */ - function numberOfBlocksInEpoch() external view override returns (uint256 _numberOfBlocks) { + function numberOfBlocksInEpoch() + public + view + override(CandidateManager, ICandidateManager) + returns (uint256 _numberOfBlocks) + { return _numberOfBlocksInEpoch; } @@ -340,22 +350,22 @@ contract RoninValidatorSet is /** * @inheritdoc IRoninValidatorSet */ - function setMaxValidatorNumber(uint256 __maxValidatorNumber) external override onlyAdmin { - _setMaxValidatorNumber(__maxValidatorNumber); + function setMaxValidatorNumber(uint256 _max) external override onlyAdmin { + _setMaxValidatorNumber(_max); } /** * @inheritdoc IRoninValidatorSet */ - function setNumberOfBlocksInEpoch(uint256 __numberOfBlocksInEpoch) external override onlyAdmin { - _setNumberOfBlocksInEpoch(__numberOfBlocksInEpoch); + function setNumberOfBlocksInEpoch(uint256 _number) external override onlyAdmin { + _setNumberOfBlocksInEpoch(_number); } /** * @inheritdoc IRoninValidatorSet */ - function setNumberOfEpochsInPeriod(uint256 __numberOfEpochsInPeriod) external override onlyAdmin { - _setNumberOfEpochsInPeriod(__numberOfEpochsInPeriod); + function setNumberOfEpochsInPeriod(uint256 _number) external override onlyAdmin { + _setNumberOfEpochsInPeriod(_number); } /** @@ -381,13 +391,16 @@ contract RoninValidatorSet is /** * @dev Returns validator candidates list. */ - function _syncNewValidatorSet() internal returns (address[] memory _candidateList) { - uint256[] memory _weights = syncCandidates(); + function _syncNewValidatorSet(bool _periodEnding) internal returns (address[] memory _candidateList) { + // This is a temporary approach since the slashing issue is still not finalized. + // Read more about slashing issue at: https://www.notion.so/skymavis/Slashing-Issue-9610ae1452434faca1213ab2e1d7d944 + uint256 _minBalance = _stakingContract.minValidatorBalance(); + uint256[] memory _weights = _filterUnsatisfiedCandidates(_periodEnding ? _minBalance : 0); _candidateList = _candidates; uint256 _length = _candidateList.length; for (uint256 _i; _i < _candidateList.length; _i++) { - if (_jailed(_candidateList[_i])) { + if (_jailed(_candidateList[_i]) || _weights[_i] < _minBalance) { _length--; _candidateList[_i] = _candidateList[_length]; _weights[_i] = _weights[_length]; @@ -408,8 +421,8 @@ contract RoninValidatorSet is * Emits the `ValidatorSetUpdated` event. * */ - function _updateValidatorSet() internal virtual { - address[] memory _candidates = _syncNewValidatorSet(); + function _updateValidatorSet(bool _periodEnding) internal virtual { + address[] memory _candidates = _syncNewValidatorSet(_periodEnding); uint256 _newValidatorCount = Math.min(_maxValidatorNumber, _candidates.length); _arrangeValidatorCandidates(_candidates, _newValidatorCount); diff --git a/test/helpers/ronin-validator-set.ts b/test/helpers/ronin-validator-set.ts index 700227af4..366789fbf 100644 --- a/test/helpers/ronin-validator-set.ts +++ b/test/helpers/ronin-validator-set.ts @@ -23,8 +23,7 @@ export class EpochController { calculateStartOfEpoch(block: number): BigNumber { return BigNumber.from( - Math.floor((block + this.minOffset + this.numberOfBlocksInEpoch - 1) / this.numberOfBlocksInEpoch) * - this.numberOfBlocksInEpoch + Math.floor((block + this.minOffset) / this.numberOfBlocksInEpoch + 1) * this.numberOfBlocksInEpoch ); } @@ -47,6 +46,9 @@ export class EpochController { } calculatePeriodOf(block: BigNumberish): BigNumber { + if (block == 0) { + return BigNumber.from(0); + } return BigNumber.from(block).div(BigNumber.from(this.numberOfBlocksInPeriod)).add(1); } diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index 050ad7ed7..65144d187 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -109,7 +109,7 @@ describe('[Integration] Slash validators', () => { slasheeInitStakingAmount = minValidatorBalance.add(slashFelonyAmount.mul(10)); await stakingContract .connect(slashee) - .proposeValidator(slashee.address, slashee.address, slashee.address, 2_00, { + .applyValidatorCandidate(slashee.address, slashee.address, slashee.address, 2_00, { value: slasheeInitStakingAmount, }); @@ -156,12 +156,6 @@ describe('[Integration] Slash validators', () => { .withArgs(slashee.address, slashFelonyAmount); }); - it('Should the Staking contract emit Undelegated event', async () => { - await expect(slashValidatorTx) - .to.emit(stakingContract, 'Undelegated') - .withArgs(slashee.address, slashee.address, slashFelonyAmount); - }); - it('Should the Staking contract subtract staked amount from validator', async () => { let _expectingSlasheeStakingAmount = slasheeInitStakingAmount.sub(slashFelonyAmount); expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(_expectingSlasheeStakingAmount); @@ -219,7 +213,7 @@ describe('[Integration] Slash validators', () => { await stakingContract .connect(slashee) - .proposeValidator(slashee.address, slashee.address, slashee.address, 2_00, { + .applyValidatorCandidate(slashee.address, slashee.address, slashee.address, 2_00, { value: slasheeInitStakingAmount, }); @@ -265,12 +259,6 @@ describe('[Integration] Slash validators', () => { .withArgs(slashee.address, slashFelonyAmount); }); - it('Should the Staking contract emit Undelegated event', async () => { - await expect(slashValidatorTx) - .to.emit(stakingContract, 'Undelegated') - .withArgs(slashee.address, slashee.address, slashFelonyAmount); - }); - it('Should the Staking contract subtract staked amount from validator', async () => { let _expectingSlasheeStakingAmount = slasheeInitStakingAmount.sub(slashFelonyAmount); expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(_expectingSlasheeStakingAmount); @@ -318,9 +306,6 @@ describe('[Integration] Slash validators', () => { }); await expect(topUpTx).to.emit(stakingContract, 'Staked').withArgs(slashee.address, slashFelonyAmount); - await expect(topUpTx) - .to.emit(stakingContract, 'Delegated') - .withArgs(slashee.address, slashee.address, slashFelonyAmount); }); // NOTE: the candidate is kicked right after the epoch is ended. diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index 714bda1fc..f9871e00b 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -89,7 +89,7 @@ describe('[Integration] Submit Block Reward', () => { validator = validatorCandidates[0]; await stakingContract .connect(validator) - .proposeValidator(validator.address, validator.address, validator.address, 2_00, { + .applyValidatorCandidate(validator.address, validator.address, validator.address, 2_00, { value: initStakingAmount, }); await mineBatchTxs(async () => { @@ -136,7 +136,7 @@ describe('[Integration] Submit Block Reward', () => { await stakingContract .connect(validator) - .proposeValidator(validator.address, validator.address, validator.address, 2_00, { + .applyValidatorCandidate(validator.address, validator.address, validator.address, 2_00, { value: initStakingAmount, }); diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index 1f674efb0..2f4d391a4 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -101,7 +101,7 @@ describe('[Integration] Wrap up epoch', () => { for (let i = 0; i < validators.length; i++) { await stakingContract .connect(validatorCandidates[i]) - .proposeValidator( + .applyValidatorCandidate( validatorCandidates[i].address, validatorCandidates[i].address, validatorCandidates[i].address, @@ -200,7 +200,7 @@ describe('[Integration] Wrap up epoch', () => { for (let i = 0; i < validators.length; i++) { await stakingContract .connect(validators[i]) - .proposeValidator(validators[i].address, validators[i].address, validators[i].address, 2_00, { + .applyValidatorCandidate(validators[i].address, validators[i].address, validators[i].address, 2_00, { value: minValidatorBalance.mul(3).add(i), }); } diff --git a/test/maintainance/Maintenance.test.ts b/test/maintainance/Maintenance.test.ts index 9e0e52611..0bf4097cb 100644 --- a/test/maintainance/Maintenance.test.ts +++ b/test/maintainance/Maintenance.test.ts @@ -60,7 +60,7 @@ describe('Maintenance test', () => { for (let i = 0; i < maxValidatorNumber; i++) { await stakingContract .connect(validatorCandidates[i]) - .proposeValidator( + .applyValidatorCandidate( validatorCandidates[i].address, validatorCandidates[i].address, validatorCandidates[i].address, diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index 242ad59bf..8e58f2026 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -116,7 +116,7 @@ describe('Slash indicator test', () => { for (let i = 0; i < maxValidatorNumber; i++) { await stakingContract .connect(validatorCandidates[i]) - .proposeValidator( + .applyValidatorCandidate( validatorCandidates[i].address, validatorCandidates[i].address, validatorCandidates[i].address, diff --git a/test/staking/CoreStaking.test.ts b/test/staking/RewardCalculation.test.ts similarity index 99% rename from test/staking/CoreStaking.test.ts rename to test/staking/RewardCalculation.test.ts index 5f095ca4f..7efd74adf 100644 --- a/test/staking/CoreStaking.test.ts +++ b/test/staking/RewardCalculation.test.ts @@ -96,7 +96,7 @@ const expectLocalCalculationRight = async () => { } }; -describe('Core Staking test', () => { +describe('Reward Calculation test', () => { let tx: ContractTransaction; const txs: ContractTransaction[] = []; diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index f79c2cb1f..a7924f96c 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -8,8 +8,6 @@ import { MockValidatorSet__factory } from '../../src/types/factories/MockValidat import { StakingVesting__factory } from '../../src/types/factories/StakingVesting__factory'; import { MockValidatorSet } from '../../src/types/MockValidatorSet'; -const EPS = 1; - let poolAddr: SignerWithAddress; let otherPoolAddr: SignerWithAddress; let deployer: SignerWithAddress; @@ -20,74 +18,7 @@ let validatorContract: MockValidatorSet; let stakingContract: Staking; let validatorCandidates: SignerWithAddress[]; -const local = { - accumulatedRewardForA: BigNumber.from(0), - accumulatedRewardForB: BigNumber.from(0), - claimableRewardForA: BigNumber.from(0), - claimableRewardForB: BigNumber.from(0), - recordReward: async function (reward: BigNumberish) { - const totalStaked = await stakingContract.totalBalance(poolAddr.address); - const stakingAmountA = await stakingContract.balanceOf(poolAddr.address, userA.address); - const stakingAmountB = await stakingContract.balanceOf(poolAddr.address, userB.address); - this.accumulatedRewardForA = this.accumulatedRewardForA.add( - BigNumber.from(reward).mul(stakingAmountA).div(totalStaked) - ); - this.accumulatedRewardForB = this.accumulatedRewardForB.add( - BigNumber.from(reward).mul(stakingAmountB).div(totalStaked) - ); - }, - commitRewardPool: function () { - this.claimableRewardForA = this.accumulatedRewardForA; - this.claimableRewardForB = this.accumulatedRewardForB; - }, - slash: function () { - this.accumulatedRewardForA = this.claimableRewardForA; - this.accumulatedRewardForB = this.claimableRewardForB; - }, - reset: function () { - this.claimableRewardForA = BigNumber.from(0); - this.claimableRewardForB = BigNumber.from(0); - this.accumulatedRewardForA = BigNumber.from(0); - this.accumulatedRewardForB = BigNumber.from(0); - }, - claimRewardForA: function () { - this.accumulatedRewardForA = this.accumulatedRewardForA.sub(this.claimableRewardForA); - this.claimableRewardForA = BigNumber.from(0); - }, - claimRewardForB: function () { - this.accumulatedRewardForB = this.accumulatedRewardForB.sub(this.claimableRewardForB); - this.claimableRewardForB = BigNumber.from(0); - }, -}; - -const expectLocalCalculationRight = async () => { - { - const userReward = await stakingContract.getTotalReward(poolAddr.address, userA.address); - expect( - userReward.sub(local.accumulatedRewardForA).abs().lte(EPS), - `invalid user reward for A expected=${local.accumulatedRewardForA.toString()} actual=${userReward}` - ).to.be.true; - const claimableReward = await stakingContract.getClaimableReward(poolAddr.address, userA.address); - expect( - claimableReward.sub(local.claimableRewardForA).abs().lte(EPS), - `invalid claimable reward for A expected=${local.claimableRewardForA.toString()} actual=${claimableReward}` - ).to.be.true; - } - { - const userReward = await stakingContract.getTotalReward(poolAddr.address, userB.address); - expect( - userReward.sub(local.accumulatedRewardForB).abs().lte(EPS), - `invalid user reward for B expected=${local.accumulatedRewardForB.toString()} actual=${userReward}` - ).to.be.true; - const claimableReward = await stakingContract.getClaimableReward(poolAddr.address, userB.address); - expect( - claimableReward.sub(local.claimableRewardForB).abs().lte(EPS), - `invalid claimable reward for B expected=${local.claimableRewardForB.toString()} actual=${claimableReward}` - ).to.be.true; - } -}; - -const minValidatorBalance = BigNumber.from(2); +const minValidatorBalance = BigNumber.from(20); const maxValidatorCandidate = 50; const numberOfEpochsInPeriod = 10; const numberOfBlocksInEpoch = 2; @@ -122,9 +53,9 @@ describe('Staking test', () => { describe('Validator candidate test', () => { it('Should not be able to propose validator with insufficient amount', async () => { - await expect(stakingContract.proposeValidator(userA.address, userA.address, userA.address, 1)).revertedWith( - 'StakingManager: insufficient amount' - ); + await expect( + stakingContract.applyValidatorCandidate(userA.address, userA.address, userA.address, 1) + ).revertedWith('StakingManager: insufficient amount'); }); it('Should be able to propose validator with sufficient amount', async () => { @@ -132,26 +63,27 @@ describe('Staking test', () => { const candidate = validatorCandidates[i]; const tx = await stakingContract .connect(candidate) - .proposeValidator( + .applyValidatorCandidate( candidate.address, candidate.address, candidate.address, 1, - /* 0.01% */ { value: minValidatorBalance } + /* 0.01% */ { value: minValidatorBalance.mul(2) } ); - await expect(tx).emit(stakingContract, 'ValidatorPoolAdded').withArgs(candidate.address, candidate.address); + await expect(tx).emit(stakingContract, 'PoolApproved').withArgs(candidate.address, candidate.address); } poolAddr = validatorCandidates[1]; - otherPoolAddr = validatorCandidates[2]; - expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance); + expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance.mul(2)); }); it('Should not be able to propose validator again', async () => { await expect( stakingContract .connect(poolAddr) - .proposeValidator(poolAddr.address, poolAddr.address, poolAddr.address, 0, { value: 10 }) + .applyValidatorCandidate(poolAddr.address, poolAddr.address, poolAddr.address, 0, { + value: minValidatorBalance, + }) ).revertedWith('CandidateManager: query for already existent candidate'); }); @@ -166,31 +98,66 @@ describe('Staking test', () => { 'StakingManager: requester must be the pool admin' ); - // TODO: fix unstake value greater than 0 - await expect(stakingContract.unstake(poolAddr.address, 0)).revertedWith( + await expect(stakingContract.unstake(poolAddr.address, 1)).revertedWith( 'StakingManager: requester must be the pool admin' ); }); - it('Should be able to stake/unstake as a validator', async () => { + it('Should be able to stake/unstake as a validator candidate', async () => { let tx: ContractTransaction; tx = await stakingContract.connect(poolAddr).stake(poolAddr.address, { value: 1 }); await expect(tx!).emit(stakingContract, 'Staked').withArgs(poolAddr.address, 1); - expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance.add(1)); + expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance.mul(2).add(1)); tx = await stakingContract.connect(poolAddr).unstake(poolAddr.address, 1); await expect(tx!).emit(stakingContract, 'Unstaked').withArgs(poolAddr.address, 1); - expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance); + expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance.mul(2)); }); it('Should be not able to unstake with the balance left is not larger than the minimum balance threshold', async () => { - await expect(stakingContract.connect(poolAddr).unstake(poolAddr.address, 2)).revertedWith( - 'StakingManager: invalid staked amount left' + await expect( + stakingContract.connect(poolAddr).unstake(poolAddr.address, minValidatorBalance.add(1)) + ).revertedWith('StakingManager: invalid staked amount left'); + }); + + it('Should not be able to request renounce using unauthorized account', async () => { + await expect(stakingContract.connect(deployer).requestRenounce(poolAddr.address)).revertedWith( + 'StakingManager: requester must be the pool admin' + ); + }); + + it('Should be able to request renounce using pool admin', async () => { + await stakingContract.connect(poolAddr).requestRenounce(poolAddr.address); + }); + + it('Should not be able to request renounce again', async () => { + await expect(stakingContract.connect(poolAddr).requestRenounce(poolAddr.address)).revertedWith( + 'CandidateManager: invalid block number' + ); + }); + + it('Should the consensus account is no longer be a candidate', async () => { + await network.provider.send('hardhat_mine', [ + ethers.utils.hexStripZeros(BigNumber.from(numberOfBlocksInEpoch * numberOfEpochsInPeriod * 2).toHexString()), + ]); + const stakedAmount = minValidatorBalance.mul(2); + expect(await stakingContract.getStakingPool(poolAddr.address)).eql([ + poolAddr.address, + stakedAmount, + stakedAmount, + ]); + await expect(() => validatorContract.wrapUpEpoch()).changeEtherBalance(poolAddr, stakedAmount); + await expect(stakingContract.getStakingPool(poolAddr.address)).revertedWith( + 'StakingManager: query for non-existent pool' ); }); }); describe('Delegator test', () => { + before(() => { + otherPoolAddr = validatorCandidates[2]; + }); + it('Should not be able to delegate with empty value', async () => { await expect(stakingContract.delegate(otherPoolAddr.address)).revertedWith( 'StakingManager: query with empty value' @@ -198,14 +165,20 @@ describe('Staking test', () => { }); it('Should not be able to delegate/undelegate when the method caller is the pool admin', async () => { - await expect(stakingContract.connect(poolAddr).delegate(poolAddr.address, { value: 1 })).revertedWith( + await expect(stakingContract.connect(otherPoolAddr).delegate(otherPoolAddr.address, { value: 1 })).revertedWith( 'StakingManager: delegator must not be the pool admin' ); - await expect(stakingContract.connect(poolAddr).undelegate(poolAddr.address, 1)).revertedWith( + await expect(stakingContract.connect(otherPoolAddr).undelegate(otherPoolAddr.address, 1)).revertedWith( 'StakingManager: delegator must not be the pool admin' ); }); + it('Should not be able to delegate to a deprecated pool', async () => { + await expect(stakingContract.delegate(poolAddr.address, { value: 1 })).revertedWith( + 'StakingManager: query for non-existent pool' + ); + }); + it('Should be able to delegate/undelegate', async () => { let tx: ContractTransaction; tx = await stakingContract.connect(userA).delegate(otherPoolAddr.address, { value: 1 }); @@ -213,122 +186,12 @@ describe('Staking test', () => { tx = await stakingContract.connect(userB).delegate(otherPoolAddr.address, { value: 1 }); await expect(tx!).emit(stakingContract, 'Delegated').withArgs(userB.address, otherPoolAddr.address, 1); - expect(await stakingContract.totalBalance(otherPoolAddr.address)).eq(minValidatorBalance.add(2)); + + expect(await stakingContract.totalBalance(otherPoolAddr.address)).eq(minValidatorBalance.mul(2).add(2)); tx = await stakingContract.connect(userA).undelegate(otherPoolAddr.address, 1); await expect(tx!).emit(stakingContract, 'Undelegated').withArgs(userA.address, otherPoolAddr.address, 1); - expect(await stakingContract.totalBalance(otherPoolAddr.address)).eq(minValidatorBalance.add(1)); - }); - }); - - describe('Reward Calculation test', () => { - before(async () => { - poolAddr = validatorCandidates[0]; - await TransparentUpgradeableProxyV2__factory.connect(stakingContract.address, proxyAdmin).functionDelegateCall( - stakingContract.interface.encodeFunctionData('setMinValidatorBalance', [0]) - ); - await stakingContract - .connect(poolAddr) - .proposeValidator(poolAddr.address, poolAddr.address, poolAddr.address, 0, { value: 0 }); - - await network.provider.send('evm_setAutomine', [false]); - }); - - after(async () => { - await network.provider.send('evm_setAutomine', [true]); - }); - - it('Should work properly with staking actions occurring sequentially for a normal period', async () => { - await stakingContract.connect(userA).delegate(poolAddr.address, { value: 100 }); - await stakingContract.connect(userB).delegate(poolAddr.address, { value: 100 }); - await stakingContract.connect(userA).delegate(otherPoolAddr.address, { value: 100 }); - await network.provider.send('evm_mine'); - - await validatorContract.connect(poolAddr).depositReward({ value: 1000 }); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await validatorContract.connect(poolAddr).depositReward({ value: 1000 }); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await stakingContract.connect(userA).delegate(poolAddr.address, { value: 200 }); - await network.provider.send('evm_mine'); - await expectLocalCalculationRight(); - - await validatorContract.connect(poolAddr).depositReward({ value: 1000 }); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await stakingContract.connect(userA).undelegate(poolAddr.address, 200); - await validatorContract.connect(poolAddr).depositReward({ value: 1000 }); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await stakingContract.connect(userA).delegate(poolAddr.address, { value: 200 }); - await network.provider.send('evm_mine'); - await local.recordReward(0); - await expectLocalCalculationRight(); - - await validatorContract.settledReward([poolAddr.address, otherPoolAddr.address]); - await validatorContract.endPeriod(); - await network.provider.send('evm_mine'); - local.commitRewardPool(); - await expectLocalCalculationRight(); - }); - - it('Should work properly with staking actions occurring sequentially for a slashed period', async () => { - await stakingContract.connect(userA).delegate(poolAddr.address, { value: 100 }); - await network.provider.send('evm_mine'); - await expectLocalCalculationRight(); - - await validatorContract.connect(poolAddr).depositReward({ value: 1000 }); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await validatorContract.connect(poolAddr).depositReward({ value: 1000 }); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await stakingContract.connect(userA).delegate(poolAddr.address, { value: 300 }); - await validatorContract.connect(poolAddr).depositReward({ value: 1000 }); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await validatorContract.slashMisdemeanor(poolAddr.address); - await network.provider.send('evm_mine'); - local.slash(); - await expectLocalCalculationRight(); - - await validatorContract.connect(poolAddr).depositReward({ value: 0 }); - await network.provider.send('evm_mine'); - await local.recordReward(0); - await expectLocalCalculationRight(); - - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - - await stakingContract.connect(userA).undelegate(poolAddr.address, 300); - await network.provider.send('evm_mine'); - await stakingContract.connect(userA).undelegate(poolAddr.address, 100); - await network.provider.send('evm_mine'); - await expectLocalCalculationRight(); - - await validatorContract.settledReward([poolAddr.address]); - await validatorContract.endPeriod(); - await network.provider.send('evm_mine'); - await expectLocalCalculationRight(); + expect(await stakingContract.totalBalance(otherPoolAddr.address)).eq(minValidatorBalance.mul(2).add(1)); }); }); }); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index 25088c0a4..08c2bb050 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -155,7 +155,7 @@ describe('Ronin Validator Set test', () => { for (let i = 0; i <= 3; i++) { await stakingContract .connect(validatorCandidates[i]) - .proposeValidator( + .applyValidatorCandidate( validatorCandidates[i].address, validatorCandidates[i].address, validatorCandidates[i].address, @@ -190,11 +190,11 @@ describe('Ronin Validator Set test', () => { it(`Should be able to wrap up epoch and pick top ${maxValidatorNumber} to be validators`, async () => { await stakingContract .connect(coinbase) - .proposeValidator(coinbase.address, coinbase.address, treasury.address, 1_00 /* 1% */, { value: 100 }); + .applyValidatorCandidate(coinbase.address, coinbase.address, treasury.address, 1_00 /* 1% */, { value: 100 }); for (let i = 4; i < validatorCandidates.length; i++) { await stakingContract .connect(validatorCandidates[i]) - .proposeValidator( + .applyValidatorCandidate( validatorCandidates[i].address, validatorCandidates[i].address, validatorCandidates[i].address, From 723b0144a50a37d74ec4f860a61162940a7de905 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 5 Oct 2022 12:30:55 +0700 Subject: [PATCH 167/190] Add Ronin Trusted Organization contract (#23) * Move contract * Stardalize comments for admin permission * Add Ronin Trusted Org contract * Update deployment script --- contracts/{slash => }/SlashIndicator.sol | 8 +- contracts/StakingVesting.sol | 4 +- .../HasRoninTrustedOrganizationContract.sol | 46 ++++ contracts/interfaces/ICandidateManager.sol | 2 +- .../interfaces/IRoninTrustedOrganization.sol | 52 +++++ contracts/interfaces/IRoninValidatorSet.sol | 26 +-- contracts/interfaces/ISlashIndicator.sol | 8 +- contracts/interfaces/IStaking.sol | 2 +- .../IHasRoninTrustedOrganizationContract.sol | 25 +++ contracts/mocks/MockSlashIndicator.sol | 76 ------- .../mocks/MockSlashIndicatorExtended.sol | 10 +- contracts/mocks/MockValidatorSet.sol | 7 - .../multi-chains/RoninTrustedOrganization.sol | 89 ++++++++ contracts/validator/RoninValidatorSet.sol | 41 +--- src/config.ts | 49 ++++- src/deploy/calculate-address.ts | 31 +-- src/deploy/logic/maintenance.ts | 7 + .../logic/ronin-trusted-organization.ts | 23 ++ src/deploy/logic/slash-indicator.ts | 6 + src/deploy/logic/staking-vesting.ts | 6 + src/deploy/logic/staking.ts | 7 + src/deploy/logic/validator-set.ts | 6 + src/deploy/proxy/maintenance-proxy.ts | 15 +- .../proxy/ronin-trusted-organization-proxy.ts | 33 +++ src/deploy/proxy/ronin-validator-proxy.ts | 22 +- src/deploy/proxy/slash-indicator-proxy.ts | 17 +- src/deploy/proxy/staking-proxy.ts | 15 +- src/deploy/proxy/staking-vesting-proxy.ts | 15 +- src/deploy/verify-address.ts | 54 ----- src/script/verify-address.ts | 7 + test/helpers/fixture.ts | 13 +- test/helpers/ronin-validator-set.ts | 19 +- .../integration/ActionSlashValidators.test.ts | 2 +- test/integration/ActionSubmitReward.test.ts | 2 +- test/integration/ActionWrapUpEpoch.test.ts | 2 +- test/slash/SlashIndicator.test.ts | 2 +- ...tors.test.ts => ArrangeValidators.test.ts} | 196 ++++++------------ test/validator/RoninValidatorSet.test.ts | 113 +++------- 38 files changed, 564 insertions(+), 494 deletions(-) rename contracts/{slash => }/SlashIndicator.sol (98%) create mode 100644 contracts/extensions/HasRoninTrustedOrganizationContract.sol create mode 100644 contracts/interfaces/IRoninTrustedOrganization.sol create mode 100644 contracts/interfaces/collections/IHasRoninTrustedOrganizationContract.sol delete mode 100644 contracts/mocks/MockSlashIndicator.sol create mode 100644 contracts/multi-chains/RoninTrustedOrganization.sol create mode 100644 src/deploy/logic/ronin-trusted-organization.ts create mode 100644 src/deploy/proxy/ronin-trusted-organization-proxy.ts delete mode 100644 src/deploy/verify-address.ts create mode 100644 src/script/verify-address.ts rename test/validator/{RoninValidatorSet-ArrangeValidators.test.ts => ArrangeValidators.test.ts} (72%) diff --git a/contracts/slash/SlashIndicator.sol b/contracts/SlashIndicator.sol similarity index 98% rename from contracts/slash/SlashIndicator.sol rename to contracts/SlashIndicator.sol index 544044b11..9d4b4ceba 100644 --- a/contracts/slash/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "../interfaces/ISlashIndicator.sol"; -import "../extensions/HasValidatorContract.sol"; -import "../extensions/HasMaintenanceContract.sol"; -import "../libraries/Math.sol"; +import "./interfaces/ISlashIndicator.sol"; +import "./extensions/HasValidatorContract.sol"; +import "./extensions/HasMaintenanceContract.sol"; +import "./libraries/Math.sol"; contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenanceContract, Initializable { using Math for uint256; diff --git a/contracts/StakingVesting.sol b/contracts/StakingVesting.sol index 7d814ebd5..b6dd5d1fa 100644 --- a/contracts/StakingVesting.sol +++ b/contracts/StakingVesting.sol @@ -20,9 +20,9 @@ contract StakingVesting is IStakingVesting, HasValidatorContract, RONTransferHel /** * @dev Initializes the contract storage. */ - function initialize(uint256 __bonusPerBlock, address __validatorContract) external payable initializer { - _setBonusPerBlock(__bonusPerBlock); + function initialize(address __validatorContract, uint256 __bonusPerBlock) external payable initializer { _setValidatorContract(__validatorContract); + _setBonusPerBlock(__bonusPerBlock); } /** diff --git a/contracts/extensions/HasRoninTrustedOrganizationContract.sol b/contracts/extensions/HasRoninTrustedOrganizationContract.sol new file mode 100644 index 000000000..13b866ccd --- /dev/null +++ b/contracts/extensions/HasRoninTrustedOrganizationContract.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./HasProxyAdmin.sol"; +import "../interfaces/collections/IHasRoninTrustedOrganizationContract.sol"; +import "../interfaces/IRoninTrustedOrganization.sol"; + +contract HasRoninTrustedOrganizationContract is IHasRoninTrustedOrganizationContract, HasProxyAdmin { + IRoninTrustedOrganization internal _roninTrustedOrganizationContract; + + modifier onlyRoninTrustedOrganizationContract() { + require( + roninTrustedOrganizationContract() == msg.sender, + "HasRoninTrustedOrganizationContract: method caller must be ronin trusted organization contract" + ); + _; + } + + /** + * @inheritdoc IHasRoninTrustedOrganizationContract + */ + function roninTrustedOrganizationContract() public view override returns (address) { + return address(_roninTrustedOrganizationContract); + } + + /** + * @inheritdoc IHasRoninTrustedOrganizationContract + */ + function setRoninTrustedOrganizationContract(address _addr) external override onlyAdmin { + _setRoninTrustedOrganizationContract(_addr); + } + + /** + * @dev Sets the ronin trusted organization contract. + * + * Requirements: + * - The new address is a contract. + * + * Emits the event `RoninTrustedOrganizationContractUpdated`. + * + */ + function _setRoninTrustedOrganizationContract(address _addr) internal { + _roninTrustedOrganizationContract = IRoninTrustedOrganization(_addr); + emit RoninTrustedOrganizationContractUpdated(_addr); + } +} diff --git a/contracts/interfaces/ICandidateManager.sol b/contracts/interfaces/ICandidateManager.sol index 482e082f0..5dbda6559 100644 --- a/contracts/interfaces/ICandidateManager.sol +++ b/contracts/interfaces/ICandidateManager.sol @@ -37,7 +37,7 @@ interface ICandidateManager { * @dev Sets the maximum number of validator candidate. * * Requirements: - * - The method caller is governance admin. + * - The method caller is admin. * * Emits the `MaxValidatorCandidateUpdated` event. * diff --git a/contracts/interfaces/IRoninTrustedOrganization.sol b/contracts/interfaces/IRoninTrustedOrganization.sol new file mode 100644 index 000000000..95d579b12 --- /dev/null +++ b/contracts/interfaces/IRoninTrustedOrganization.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IRoninTrustedOrganization { + /// @dev Emitted when the trusted organization is added. + event TrustedOrganizationAdded(address); + /// @dev Emitted when the trusted organization is removed. + event TrustedOrganizationRemoved(address); + + /** + * @dev Adds a list of addresses into the trusted organization. + * + * Requirements: + * - The method caller is admin. + * + * Emits the event `TrustedOrganizationAdded` once an organization is added. + * + */ + function addTrustedOrganizations(address[] calldata) external; + + /** + * @dev Removes a list of addresses from the trusted organization. + * + * Requirements: + * - The method caller is admin. + * + * Emits the event `TrustedOrganizationRemoved` once an organization is removed. + * + */ + function removeTrustedOrganizations(address[] calldata) external; + + /** + * @dev Returns whether the addresses are trusted organizations. + */ + function isTrustedOrganizations(address[] calldata) external view returns (bool[] memory); + + /** + * @dev Returns the trusted organization at `_index`. + */ + function getTrustedOrganizationAt(uint256 _index) external view returns (address); + + /** + * @dev Returns the number of trusted organizations. + */ + function countTrustedOrganizations() external view returns (uint256); + + /** + * @dev Returns all of the trusted organization addresses. + */ + function getAllTrustedOrganizations() external view returns (address[] memory); +} diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index a292b765a..3dde87fdb 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -25,8 +25,6 @@ interface IRoninValidatorSet is ICandidateManager { event MiningRewardDistributed(address validatorAddr, uint256 amount); /// @dev Emitted when the amount of RON reward is distributed. event StakingRewardDistributed(uint256 amount); - /// @dev Emitted when the priority status of addresses is updated - event AddressesPriorityStatusUpdated(address[], bool[]); /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR COINBASE // @@ -137,20 +135,15 @@ interface IRoninValidatorSet is ICandidateManager { */ function periodEndingAt(uint256 _block) external view returns (bool); - /** - * @dev Returns whether an `_addr` is whether prioritized. - */ - function getPriorityStatus(address _addr) external view returns (bool); - /////////////////////////////////////////////////////////////////////////////////////// - // FUNCTIONS FOR GOVERNANCE ADMIN // + // FUNCTIONS FOR ADMIN // /////////////////////////////////////////////////////////////////////////////////////// /** * @dev Updates the max validator number * * Requirements: - * - The method caller is the governance admin + * - The method caller is admin * * Emits the event `MaxValidatorNumberUpdated` * @@ -161,7 +154,7 @@ interface IRoninValidatorSet is ICandidateManager { * @dev Updates the number of blocks in epoch * * Requirements: - * - The method caller is the governance admin + * - The method caller is admin * * Emits the event `NumberOfBlocksInEpochUpdated` * @@ -172,21 +165,10 @@ interface IRoninValidatorSet is ICandidateManager { * @dev Updates the number of epochs in period * * Requirements: - * - The method caller is the governance admin + * - The method caller is admin * * Emits the event `NumberOfEpochsInPeriodUpdated` * */ function setNumberOfEpochsInPeriod(uint256 _numberOfEpochsInPeriod) external; - - /** - * @dev Updates the status to an array of addresses that they will be prioritized or not - * - * Requirements: - * - The method caller is the governance admin - * - * Emits the event `AddressesPriorityStatusUpdated` for updated addresses - * - */ - function setPrioritizedAddresses(address[] memory _addresses, bool[] memory _statuses) external; } diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index 22337502f..2fa75ccb0 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -88,7 +88,7 @@ interface ISlashIndicator { * @dev Sets the slash thresholds * * Requirements: - * - Only governance admin can call this method + * - Only admin can call this method * * Emits the event `SlashThresholdsUpdated` * @@ -99,7 +99,7 @@ interface ISlashIndicator { * @dev Sets the slash felony amount * * Requirements: - * - Only governance admin can call this method + * - Only admin can call this method * * Emits the event `SlashFelonyAmountUpdated` * @@ -110,7 +110,7 @@ interface ISlashIndicator { * @dev Sets the slash double sign amount * * Requirements: - * - Only governance admin can call this method + * - Only admin can call this method * * Emits the event `SlashDoubleSignAmountUpdated` * @@ -121,7 +121,7 @@ interface ISlashIndicator { * @dev Sets the felony jail duration * * Requirements: - * - Only governance admin can call this method + * - Only admin can call this method * * Emits the event `FelonyJailDurationUpdated` * diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 0addf5505..a98e37344 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -48,7 +48,7 @@ interface IStaking is IRewardPool { * @dev Sets the minimum threshold for being a validator candidate. * * Requirements: - * - The method caller is governance admin. + * - The method caller is admin. * * Emits the `MinValidatorBalanceUpdated` event. * diff --git a/contracts/interfaces/collections/IHasRoninTrustedOrganizationContract.sol b/contracts/interfaces/collections/IHasRoninTrustedOrganizationContract.sol new file mode 100644 index 000000000..ba254716e --- /dev/null +++ b/contracts/interfaces/collections/IHasRoninTrustedOrganizationContract.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IHasRoninTrustedOrganizationContract { + /// @dev Emitted when the ronin trusted organization contract is updated. + event RoninTrustedOrganizationContractUpdated(address); + + /** + * @dev Returns the ronin trusted organization contract. + */ + function roninTrustedOrganizationContract() external view returns (address); + + /** + * @dev Sets the ronin trusted organization contract. + * + * Requirements: + * - The method caller is admin. + * - The new address is a contract. + * + * Emits the event `RoninTrustedOrganizationContractUpdated`. + * + */ + function setRoninTrustedOrganizationContract(address) external; +} diff --git a/contracts/mocks/MockSlashIndicator.sol b/contracts/mocks/MockSlashIndicator.sol deleted file mode 100644 index 09aee9eb4..000000000 --- a/contracts/mocks/MockSlashIndicator.sol +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -import "../interfaces/IRoninValidatorSet.sol"; -import "../interfaces/ISlashIndicator.sol"; - -contract MockSlashIndicator is ISlashIndicator { - IRoninValidatorSet public validatorContract; - uint256 public slashFelonyAmount; - uint256 public slashDoubleSignAmount; - - modifier onlyCoinbase() { - require(msg.sender == block.coinbase, "SlashIndicator: method caller must be coinbase"); - _; - } - - constructor( - IRoninValidatorSet _validatorSetContract, - uint256 _slashFelonyAmount, - uint256 _slashDoubleSignAmount - ) { - validatorContract = _validatorSetContract; - slashFelonyAmount = _slashFelonyAmount; - slashDoubleSignAmount = _slashDoubleSignAmount; - } - - function slashFelony(address _validatorAddr) external { - validatorContract.slash(_validatorAddr, 0, 0); - } - - function slashMisdemeanor(address _validatorAddr) external { - validatorContract.slash(_validatorAddr, 0, 0); - } - - function slash(address _valAddr) external override {} - - function getUnavailabilityThresholds() - external - view - override - returns (uint256 misdemeanorThreshold, uint256 felonyThreshold) - {} - - function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external override {} - - function setSlashFelonyAmount(uint256 _slashFelonyAmount) external override {} - - function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external override {} - - function setFelonyJailDuration(uint256 _felonyJailDuration) external override {} - - function currentUnavailabilityIndicator(address _validator) external view override returns (uint256) {} - - function getUnavailabilityIndicator(address _validator, uint256 _period) external view override returns (uint256) {} - - function getUnavailabilitySlashType(address _validatorAddr, uint256 _period) - external - view - override - returns (SlashType) - {} - - function unavailabilityThresholdsOf(address _addr, uint256 _block) - external - view - override - returns (uint256 _felonyThreshold, uint256 _misdemeanorThreshold) - {} - - function slashDoubleSign( - address _validatorAddr, - BlockHeader calldata _header1, - BlockHeader calldata _header2 - ) external override {} -} diff --git a/contracts/mocks/MockSlashIndicatorExtended.sol b/contracts/mocks/MockSlashIndicatorExtended.sol index d4b4aaef7..b8efec658 100644 --- a/contracts/mocks/MockSlashIndicatorExtended.sol +++ b/contracts/mocks/MockSlashIndicatorExtended.sol @@ -2,9 +2,17 @@ pragma solidity ^0.8.9; -import "../slash/SlashIndicator.sol"; +import "../SlashIndicator.sol"; contract MockSlashIndicatorExtended is SlashIndicator { + function slashFelony(address _validatorAddr) external { + _validatorContract.slash(_validatorAddr, 0, 0); + } + + function slashMisdemeanor(address _validatorAddr) external { + _validatorContract.slash(_validatorAddr, 0, 0); + } + function _validateEvidence( BlockHeader memory, /*_header1*/ BlockHeader memory /*_header2*/ diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index c52a026ce..cb6c21afb 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -107,13 +107,6 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { returns (uint256 _maximumPrioritizedValidatorNumber) {} - function setPrioritizedAddresses(address[] memory __validatorAddresses, bool[] memory __prioritizedList) - external - override - {} - - function getPriorityStatus(address _addr) external view override returns (bool) {} - function isValidator(address) external pure override returns (bool) { return true; } diff --git a/contracts/multi-chains/RoninTrustedOrganization.sol b/contracts/multi-chains/RoninTrustedOrganization.sol new file mode 100644 index 000000000..032435057 --- /dev/null +++ b/contracts/multi-chains/RoninTrustedOrganization.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import "../interfaces/IRoninTrustedOrganization.sol"; +import "../extensions/HasProxyAdmin.sol"; + +contract RoninTrustedOrganization is IRoninTrustedOrganization, HasProxyAdmin, Initializable { + using EnumerableSet for EnumerableSet.AddressSet; + + EnumerableSet.AddressSet internal _orgs; + + /** + * @dev Initializes the contract storage. + */ + function initialize(address[] calldata _trustedOrgs) external initializer { + _addTrustedOrganizations(_trustedOrgs); + } + + /** + * @inheritdoc IRoninTrustedOrganization + */ + function addTrustedOrganizations(address[] calldata _list) external override onlyAdmin { + _addTrustedOrganizations(_list); + } + + /** + * @inheritdoc IRoninTrustedOrganization + */ + function removeTrustedOrganizations(address[] calldata _list) external override onlyAdmin { + if (_list.length == 0) { + return; + } + + for (uint _i = 0; _i < _list.length; _i++) { + if (_orgs.remove(_list[_i])) { + emit TrustedOrganizationRemoved(_list[_i]); + } + } + } + + /** + * @inheritdoc IRoninTrustedOrganization + */ + function isTrustedOrganizations(address[] calldata _list) external view override returns (bool[] memory _res) { + _res = new bool[](_list.length); + for (uint _i = 0; _i < _res.length; _i++) { + _res[_i] = _orgs.contains(_list[_i]); + } + } + + /** + * @inheritdoc IRoninTrustedOrganization + */ + function getTrustedOrganizationAt(uint256 _idx) external view override returns (address) { + return _orgs.at(_idx); + } + + /** + * @inheritdoc IRoninTrustedOrganization + */ + function countTrustedOrganizations() external view override returns (uint256) { + return _orgs.length(); + } + + /** + * @inheritdoc IRoninTrustedOrganization + */ + function getAllTrustedOrganizations() external view override returns (address[] memory) { + return _orgs.values(); + } + + /** + * @dev Adds a list of addresses into the trusted organization. + */ + function _addTrustedOrganizations(address[] calldata _list) internal { + if (_list.length == 0) { + return; + } + + for (uint _i = 0; _i < _list.length; _i++) { + if (_orgs.add(_list[_i])) { + emit TrustedOrganizationAdded(_list[_i]); + } + } + } +} diff --git a/contracts/validator/RoninValidatorSet.sol b/contracts/validator/RoninValidatorSet.sol index 5ce63f4e4..ac6bfcc5a 100644 --- a/contracts/validator/RoninValidatorSet.sol +++ b/contracts/validator/RoninValidatorSet.sol @@ -9,6 +9,7 @@ import "../extensions/HasStakingVestingContract.sol"; import "../extensions/HasStakingContract.sol"; import "../extensions/HasSlashIndicatorContract.sol"; import "../extensions/HasMaintenanceContract.sol"; +import "../extensions/HasRoninTrustedOrganizationContract.sol"; import "../interfaces/IRoninValidatorSet.sol"; import "../libraries/Sorting.sol"; import "../libraries/Math.sol"; @@ -21,6 +22,7 @@ contract RoninValidatorSet is HasStakingVestingContract, HasSlashIndicatorContract, HasMaintenanceContract, + HasRoninTrustedOrganizationContract, CandidateManager, Initializable { @@ -39,9 +41,6 @@ contract RoninValidatorSet is mapping(uint256 => address) internal _validator; /// @dev Mapping from address => flag indicating whether the address is validator or not mapping(address => bool) internal _validatorMap; - - /// @dev Mapping from address => flag indicating whether the address is prioritized to be a validator - mapping(address => bool) internal _prioritizedRegisterredMap; /// @dev The number of slot that is reserved for prioritized validators uint256 internal _maxPrioritizedValidatorNumber; @@ -85,6 +84,7 @@ contract RoninValidatorSet is address __stakingContract, address __stakingVestingContract, address __maintenanceContract, + address __roninTrustedOrganizationContract, uint256 __maxValidatorNumber, uint256 __maxValidatorCandidate, uint256 __maxPrioritizedValidatorNumber, @@ -95,6 +95,7 @@ contract RoninValidatorSet is _setStakingContract(__stakingContract); _setStakingVestingContract(__stakingVestingContract); _setMaintenanceContract(__maintenanceContract); + _setRoninTrustedOrganizationContract(__roninTrustedOrganizationContract); _setMaxValidatorNumber(__maxValidatorNumber); _setMaxValidatorCandidate(__maxValidatorCandidate); _setPrioritizedValidatorNumber(__maxPrioritizedValidatorNumber); @@ -336,15 +337,8 @@ contract RoninValidatorSet is return _maxPrioritizedValidatorNumber; } - /** - * @inheritdoc IRoninValidatorSet - */ - function getPriorityStatus(address _addr) external view override returns (bool) { - return _prioritizedRegisterredMap[_addr]; - } - /////////////////////////////////////////////////////////////////////////////////////// - // FUNCTIONS FOR GOVERNANCE ADMIN // + // FUNCTIONS FOR ADMIN // /////////////////////////////////////////////////////////////////////////////////////// /** @@ -368,22 +362,6 @@ contract RoninValidatorSet is _setNumberOfEpochsInPeriod(_number); } - /** - * @inheritdoc IRoninValidatorSet - */ - function setPrioritizedAddresses(address[] memory _addrs, bool[] memory _statuses) external override onlyAdmin { - require(_addrs.length != 0, "RoninValidatorSet: empty array"); - require(_addrs.length == _statuses.length, "RoninValidatorSet: length of two input arrays mismatches"); - - for (uint _i = 0; _i < _addrs.length; _i++) { - if (_prioritizedRegisterredMap[_addrs[_i]] != _statuses[_i]) { - _prioritizedRegisterredMap[_addrs[_i]] = _statuses[_i]; - } - } - - emit AddressesPriorityStatusUpdated(_addrs, _statuses); - } - /////////////////////////////////////////////////////////////////////////////////////// // HELPER FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// @@ -489,12 +467,11 @@ contract RoninValidatorSet is uint _waitingCounter; uint _prioritySlotCounter; + bool[] memory _isTrustedOrgs = _roninTrustedOrganizationContract.isTrustedOrganizations(_candidates); for (uint _i = 0; _i < _candidates.length; _i++) { - if (_prioritizedRegisterredMap[_candidates[_i]]) { - if (_prioritySlotCounter < _maxPrioritizedValidatorNumber) { - _candidates[_prioritySlotCounter++] = _candidates[_i]; - continue; - } + if (_isTrustedOrgs[_i] && _prioritySlotCounter < _maxPrioritizedValidatorNumber) { + _candidates[_prioritySlotCounter++] = _candidates[_i]; + continue; } _waitingCandidates[_waitingCounter++] = _candidates[_i]; } diff --git a/src/config.ts b/src/config.ts index dfb01c9d1..d272f3cde 100644 --- a/src/config.ts +++ b/src/config.ts @@ -7,28 +7,55 @@ export enum Network { Devnet = 'ronin-devnet', Testnet = 'ronin-testnet', Mainnet = 'ronin-mainnet', + Goerli = 'goerli', + Ethereum = 'ethereum', } export type LiteralNetwork = Network | string; +export const commonNetworks: LiteralNetwork[] = [Network.Hardhat, Network.Devnet]; +export const mainchainNetworks: LiteralNetwork[] = [...commonNetworks, Network.Goerli, Network.Ethereum]; +export const roninchainNetworks: LiteralNetwork[] = [...commonNetworks, Network.Testnet, Network.Mainnet]; +export const allNetworks: LiteralNetwork[] = [ + ...commonNetworks, + ...mainchainNetworks.slice(commonNetworks.length), + ...roninchainNetworks.slice(commonNetworks.length), +]; + export const defaultAddress = '0x0000000000000000000000000000000000000000'; +export interface AddressExtended { + address: Address; + nonce?: number; +} + export interface InitAddr { [network: LiteralNetwork]: { governanceAdmin: Address; - maintenanceContract?: Address; - stakingVestingContract?: Address; - slashIndicatorContract?: Address; - stakingContract?: Address; - validatorContract?: Address; + maintenanceContract?: AddressExtended; + stakingVestingContract?: AddressExtended; + slashIndicatorContract?: AddressExtended; + stakingContract?: AddressExtended; + validatorContract?: AddressExtended; + roninTrustedOrganizationContract?: AddressExtended; }; } + export interface MaintenanceArguments { minMaintenanceBlockPeriod?: BigNumberish; maxMaintenanceBlockPeriod?: BigNumberish; minOffset?: BigNumberish; maxSchedules?: BigNumberish; } + +export interface RoninTrustedOrganizationArguments { + trustedOrganizations?: Address[]; +} + +export interface RoninTrustedOrganizationConfig { + [network: LiteralNetwork]: RoninTrustedOrganizationArguments | undefined; +} + export interface MaintenanceConfig { [network: LiteralNetwork]: MaintenanceArguments | undefined; } @@ -146,3 +173,15 @@ export const roninValidatorSetConf: RoninValidatorSetConfig = { [Network.Testnet]: undefined, [Network.Mainnet]: undefined, }; + +// TODO: update config for testnet, mainnet, goerli, ethereum +export const roninTrustedOrganizationConf: RoninTrustedOrganizationConfig = { + [Network.Hardhat]: undefined, + [Network.Devnet]: { + trustedOrganizations: [], // trusted no one + }, + [Network.Testnet]: undefined, + [Network.Mainnet]: undefined, + [Network.Goerli]: undefined, + [Network.Ethereum]: undefined, +}; diff --git a/src/deploy/calculate-address.ts b/src/deploy/calculate-address.ts index 02d8fa1c0..aa6a881cb 100644 --- a/src/deploy/calculate-address.ts +++ b/src/deploy/calculate-address.ts @@ -1,25 +1,24 @@ import { ethers, network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { initAddress } from '../config'; +import { allNetworks, initAddress, roninchainNetworks } from '../config'; + +const calculateAddress = (from: string, nonce: number) => ({ + nonce, + address: ethers.utils.getContractAddress({ from, nonce }), +}); const deploy = async ({ getNamedAccounts }: HardhatRuntimeEnvironment) => { const { deployer } = await getNamedAccounts(); let nonce = await ethers.provider.getTransactionCount(deployer); - initAddress[network.name].maintenanceContract = ethers.utils.getContractAddress({ - from: deployer, - nonce: nonce++, - }); - initAddress[network.name].stakingVestingContract = ethers.utils.getContractAddress({ - from: deployer, - nonce: nonce++, - }); - initAddress[network.name].slashIndicatorContract = ethers.utils.getContractAddress({ - from: deployer, - nonce: nonce++, - }); - initAddress[network.name].stakingContract = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++ }); - initAddress[network.name].validatorContract = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++ }); + + if (roninchainNetworks.includes(network.name!)) { + initAddress[network.name].maintenanceContract = calculateAddress(deployer, nonce++); + initAddress[network.name].stakingVestingContract = calculateAddress(deployer, nonce++); + initAddress[network.name].slashIndicatorContract = calculateAddress(deployer, nonce++); + initAddress[network.name].stakingContract = calculateAddress(deployer, nonce++); + initAddress[network.name].validatorContract = calculateAddress(deployer, nonce++); + } }; deploy.tags = ['CalculateAddresses']; @@ -29,6 +28,8 @@ deploy.dependencies = [ 'SlashIndicatorLogic', 'StakingLogic', 'RoninValidatorSetLogic', + 'RoninTrustedOrganizationLogic', + 'RoninTrustedOrganizationProxy', ]; export default deploy; diff --git a/src/deploy/logic/maintenance.ts b/src/deploy/logic/maintenance.ts index ea2ff7ae8..a9ad69d17 100644 --- a/src/deploy/logic/maintenance.ts +++ b/src/deploy/logic/maintenance.ts @@ -1,6 +1,13 @@ +import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { roninchainNetworks } from '../../config'; + const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!roninchainNetworks.includes(network.name!)) { + return; + } + const { deploy } = deployments; const { deployer } = await getNamedAccounts(); diff --git a/src/deploy/logic/ronin-trusted-organization.ts b/src/deploy/logic/ronin-trusted-organization.ts new file mode 100644 index 000000000..85d1813d3 --- /dev/null +++ b/src/deploy/logic/ronin-trusted-organization.ts @@ -0,0 +1,23 @@ +import { network } from 'hardhat'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +import { allNetworks } from '../../config'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!allNetworks.includes(network.name!)) { + return; + } + + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + await deploy('RoninTrustedOrganizationLogic', { + contract: 'RoninTrustedOrganization', + from: deployer, + log: true, + }); +}; + +deploy.tags = ['RoninTrustedOrganizationLogic']; + +export default deploy; diff --git a/src/deploy/logic/slash-indicator.ts b/src/deploy/logic/slash-indicator.ts index 1a4fbcb4d..f9d94a3fb 100644 --- a/src/deploy/logic/slash-indicator.ts +++ b/src/deploy/logic/slash-indicator.ts @@ -1,6 +1,12 @@ +import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { roninchainNetworks } from '../../config'; const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!roninchainNetworks.includes(network.name!)) { + return; + } + const { deploy } = deployments; const { deployer } = await getNamedAccounts(); diff --git a/src/deploy/logic/staking-vesting.ts b/src/deploy/logic/staking-vesting.ts index 423377664..36d492730 100644 --- a/src/deploy/logic/staking-vesting.ts +++ b/src/deploy/logic/staking-vesting.ts @@ -1,6 +1,12 @@ +import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { roninchainNetworks } from '../../config'; const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!roninchainNetworks.includes(network.name!)) { + return; + } + const { deploy } = deployments; const { deployer } = await getNamedAccounts(); diff --git a/src/deploy/logic/staking.ts b/src/deploy/logic/staking.ts index 7199cfff7..d5784daae 100644 --- a/src/deploy/logic/staking.ts +++ b/src/deploy/logic/staking.ts @@ -1,6 +1,13 @@ +import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { roninchainNetworks } from '../../config'; + const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!roninchainNetworks.includes(network.name!)) { + return; + } + const { deploy } = deployments; const { deployer } = await getNamedAccounts(); diff --git a/src/deploy/logic/validator-set.ts b/src/deploy/logic/validator-set.ts index a741db6ac..0bcb07d37 100644 --- a/src/deploy/logic/validator-set.ts +++ b/src/deploy/logic/validator-set.ts @@ -1,6 +1,12 @@ +import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { roninchainNetworks } from '../../config'; + const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!roninchainNetworks.includes(network.name!)) { + return; + } const { deploy } = deployments; const { deployer } = await getNamedAccounts(); diff --git a/src/deploy/proxy/maintenance-proxy.ts b/src/deploy/proxy/maintenance-proxy.ts index 22d48cf55..91e3a1634 100644 --- a/src/deploy/proxy/maintenance-proxy.ts +++ b/src/deploy/proxy/maintenance-proxy.ts @@ -1,32 +1,39 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { maintenanceConf, initAddress } from '../../config'; +import { maintenanceConf, initAddress, roninchainNetworks } from '../../config'; +import { verifyAddress } from '../../script/verify-address'; import { Maintenance__factory } from '../../types'; const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!roninchainNetworks.includes(network.name!)) { + return; + } + const { deploy } = deployments; const { deployer } = await getNamedAccounts(); const logicContract = await deployments.get('MaintenanceLogic'); const data = new Maintenance__factory().interface.encodeFunctionData('initialize', [ - initAddress[network.name]!.validatorContract, + initAddress[network.name]!.validatorContract?.address, maintenanceConf[network.name]!.minMaintenanceBlockPeriod, maintenanceConf[network.name]!.maxMaintenanceBlockPeriod, maintenanceConf[network.name]!.minOffset, maintenanceConf[network.name]!.maxSchedules, ]); - await deploy('MaintenanceProxy', { + const deployment = await deploy('MaintenanceProxy', { contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], + nonce: initAddress[network.name].maintenanceContract?.nonce, }); + verifyAddress(deployment.address, initAddress[network.name].maintenanceContract?.address); }; deploy.tags = ['MaintenanceProxy']; -deploy.dependencies = ['MaintenanceLogic']; +deploy.dependencies = ['MaintenanceLogic', 'CalculateAddresses']; export default deploy; diff --git a/src/deploy/proxy/ronin-trusted-organization-proxy.ts b/src/deploy/proxy/ronin-trusted-organization-proxy.ts new file mode 100644 index 000000000..1fbdbacef --- /dev/null +++ b/src/deploy/proxy/ronin-trusted-organization-proxy.ts @@ -0,0 +1,33 @@ +import { network } from 'hardhat'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +import { roninTrustedOrganizationConf, initAddress, allNetworks } from '../../config'; +import { RoninTrustedOrganization__factory } from '../../types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!allNetworks.includes(network.name!)) { + return; + } + + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + const logicContract = await deployments.get('RoninTrustedOrganizationLogic'); + + const data = new RoninTrustedOrganization__factory().interface.encodeFunctionData('initialize', [ + roninTrustedOrganizationConf[network.name]!.trustedOrganizations, + ]); + + const deployment = await deploy('RoninTrustedOrganizationProxy', { + contract: 'TransparentUpgradeableProxyV2', + from: deployer, + log: true, + args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], + }); + initAddress[network.name].roninTrustedOrganizationContract = { address: deployment.address }; +}; + +deploy.tags = ['RoninTrustedOrganizationProxy']; +deploy.dependencies = ['RoninTrustedOrganizationLogic']; + +export default deploy; diff --git a/src/deploy/proxy/ronin-validator-proxy.ts b/src/deploy/proxy/ronin-validator-proxy.ts index 0994d261e..1084a607d 100644 --- a/src/deploy/proxy/ronin-validator-proxy.ts +++ b/src/deploy/proxy/ronin-validator-proxy.ts @@ -1,20 +1,26 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { roninValidatorSetConf, initAddress } from '../../config'; +import { roninValidatorSetConf, initAddress, roninchainNetworks } from '../../config'; +import { verifyAddress } from '../../script/verify-address'; import { RoninValidatorSet__factory } from '../../types'; const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!roninchainNetworks.includes(network.name!)) { + return; + } + const { deploy } = deployments; const { deployer } = await getNamedAccounts(); const logicContract = await deployments.get('RoninValidatorSetLogic'); const data = new RoninValidatorSet__factory().interface.encodeFunctionData('initialize', [ - initAddress[network.name]!.slashIndicatorContract, - initAddress[network.name]!.stakingContract, - initAddress[network.name]!.stakingVestingContract, - initAddress[network.name]!.maintenanceContract, + initAddress[network.name]!.slashIndicatorContract?.address, + initAddress[network.name]!.stakingContract?.address, + initAddress[network.name]!.stakingVestingContract?.address, + initAddress[network.name]!.maintenanceContract?.address, + initAddress[network.name]!.roninTrustedOrganizationContract?.address, roninValidatorSetConf[network.name]!.maxValidatorNumber, roninValidatorSetConf[network.name]!.maxValidatorCandidate, roninValidatorSetConf[network.name]!.maxPrioritizedValidatorNumber, @@ -22,15 +28,17 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme roninValidatorSetConf[network.name]!.numberOfEpochsInPeriod, ]); - await deploy('RoninValidatorSetProxy', { + const deployment = await deploy('RoninValidatorSetProxy', { contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], + nonce: initAddress[network.name].validatorContract?.nonce, }); + verifyAddress(deployment.address, initAddress[network.name].validatorContract?.address); }; deploy.tags = ['RoninValidatorSetProxy']; -deploy.dependencies = ['RoninValidatorSetLogic', 'StakingProxy']; +deploy.dependencies = ['RoninValidatorSetLogic', 'CalculateAddresses', 'StakingProxy']; export default deploy; diff --git a/src/deploy/proxy/slash-indicator-proxy.ts b/src/deploy/proxy/slash-indicator-proxy.ts index 86d1f14d0..13932c401 100644 --- a/src/deploy/proxy/slash-indicator-proxy.ts +++ b/src/deploy/proxy/slash-indicator-proxy.ts @@ -1,18 +1,23 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { slashIndicatorConf, initAddress } from '../../config'; +import { slashIndicatorConf, initAddress, roninchainNetworks } from '../../config'; +import { verifyAddress } from '../../script/verify-address'; import { SlashIndicator__factory } from '../../types'; const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!roninchainNetworks.includes(network.name!)) { + return; + } + const { deploy } = deployments; const { deployer } = await getNamedAccounts(); const logicContract = await deployments.get('SlashIndicatorLogic'); const data = new SlashIndicator__factory().interface.encodeFunctionData('initialize', [ - initAddress[network.name]!.validatorContract, - initAddress[network.name]!.maintenanceContract, + initAddress[network.name]!.validatorContract?.address, + initAddress[network.name]!.maintenanceContract?.address, slashIndicatorConf[network.name]!.misdemeanorThreshold, slashIndicatorConf[network.name]!.felonyThreshold, slashIndicatorConf[network.name]!.slashFelonyAmount, @@ -21,15 +26,17 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme slashIndicatorConf[network.name]!.doubleSigningConstrainBlocks, ]); - await deploy('SlashIndicatorProxy', { + const deployment = await deploy('SlashIndicatorProxy', { contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], + nonce: initAddress[network.name].slashIndicatorContract?.nonce, }); + verifyAddress(deployment.address, initAddress[network.name].slashIndicatorContract?.address); }; deploy.tags = ['SlashIndicatorProxy']; -deploy.dependencies = ['SlashIndicatorLogic', 'StakingVestingProxy']; +deploy.dependencies = ['SlashIndicatorLogic', 'CalculateAddresses', 'StakingVestingProxy']; export default deploy; diff --git a/src/deploy/proxy/staking-proxy.ts b/src/deploy/proxy/staking-proxy.ts index 14e0cc539..3a0002db2 100644 --- a/src/deploy/proxy/staking-proxy.ts +++ b/src/deploy/proxy/staking-proxy.ts @@ -1,29 +1,36 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { stakingConfig, initAddress } from '../../config'; +import { stakingConfig, initAddress, roninchainNetworks } from '../../config'; +import { verifyAddress } from '../../script/verify-address'; import { Staking__factory } from '../../types'; const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!roninchainNetworks.includes(network.name!)) { + return; + } + const { deploy } = deployments; const { deployer } = await getNamedAccounts(); const logicContract = await deployments.get('StakingLogic'); const data = new Staking__factory().interface.encodeFunctionData('initialize', [ - initAddress[network.name]!.validatorContract, + initAddress[network.name]!.validatorContract?.address, stakingConfig[network.name]!.minValidatorBalance, ]); - await deploy('StakingProxy', { + const deployment = await deploy('StakingProxy', { contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], + nonce: initAddress[network.name].stakingContract?.nonce, }); + verifyAddress(deployment.address, initAddress[network.name].stakingContract?.address); }; deploy.tags = ['StakingProxy']; -deploy.dependencies = ['StakingLogic', 'SlashIndicatorProxy']; +deploy.dependencies = ['StakingLogic', 'CalculateAddresses', 'SlashIndicatorProxy']; export default deploy; diff --git a/src/deploy/proxy/staking-vesting-proxy.ts b/src/deploy/proxy/staking-vesting-proxy.ts index b74825c85..887165c2e 100644 --- a/src/deploy/proxy/staking-vesting-proxy.ts +++ b/src/deploy/proxy/staking-vesting-proxy.ts @@ -2,30 +2,37 @@ import { BigNumber } from 'ethers'; import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { initAddress, stakingVestingConfig } from '../../config'; +import { initAddress, roninchainNetworks, stakingVestingConfig } from '../../config'; +import { verifyAddress } from '../../script/verify-address'; import { StakingVesting__factory } from '../../types'; const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!roninchainNetworks.includes(network.name!)) { + return; + } + const { deploy } = deployments; const { deployer } = await getNamedAccounts(); const logicContract = await deployments.get('StakingVestingLogic'); const data = new StakingVesting__factory().interface.encodeFunctionData('initialize', [ + initAddress[network.name]!.validatorContract?.address, stakingVestingConfig[network.name]!.bonusPerBlock, - initAddress[network.name]!.validatorContract, ]); - await deploy('StakingVestingProxy', { + const deployment = await deploy('StakingVestingProxy', { contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], value: BigNumber.from(stakingVestingConfig[network.name]!.topupAmount), + nonce: initAddress[network.name].stakingVestingContract?.nonce, }); + verifyAddress(deployment.address, initAddress[network.name].stakingVestingContract?.address); }; deploy.tags = ['StakingVestingProxy']; -deploy.dependencies = ['StakingVestingLogic', 'MaintenanceProxy']; +deploy.dependencies = ['StakingVestingLogic', 'CalculateAddresses', 'MaintenanceProxy']; export default deploy; diff --git a/src/deploy/verify-address.ts b/src/deploy/verify-address.ts deleted file mode 100644 index 846834919..000000000 --- a/src/deploy/verify-address.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { network } from 'hardhat'; -import { HardhatRuntimeEnvironment } from 'hardhat/types'; - -import { initAddress } from '../config'; - -const deploy = async ({ deployments }: HardhatRuntimeEnvironment) => { - const MaintenanceContract = await deployments.get('MaintenanceProxy'); - const stakingVestingContract = await deployments.get('StakingVestingProxy'); - const slashIndicatorContract = await deployments.get('SlashIndicatorProxy'); - const stakingContract = await deployments.get('StakingProxy'); - const validatorContract = await deployments.get('RoninValidatorSetProxy'); - - if (initAddress[network.name].maintenanceContract?.toLowerCase() != MaintenanceContract.address.toLowerCase()) { - throw Error( - `invalid address for indicator, expected=${initAddress[ - network.name - ].maintenanceContract?.toLowerCase()}, actual=${MaintenanceContract.address.toLowerCase()}` - ); - } - if (initAddress[network.name].slashIndicatorContract?.toLowerCase() != slashIndicatorContract.address.toLowerCase()) { - throw Error( - `invalid address for slashIndicator, expected=${initAddress[ - network.name - ].slashIndicatorContract?.toLowerCase()}, actual=${slashIndicatorContract.address.toLowerCase()}` - ); - } - if (initAddress[network.name].stakingVestingContract?.toLowerCase() != stakingVestingContract.address.toLowerCase()) { - throw Error( - `invalid address for stakingVestingContract, expected=${initAddress[ - network.name - ].stakingVestingContract?.toLowerCase()}, actual=${stakingVestingContract.address.toLowerCase()}` - ); - } - if (initAddress[network.name].stakingContract?.toLowerCase() != stakingContract.address.toLowerCase()) { - throw Error( - `invalid address for stakingContract, expected=${initAddress[ - network.name - ].stakingContract?.toLowerCase()}, actual=${stakingContract.address.toLowerCase()}` - ); - } - if (initAddress[network.name].validatorContract?.toLowerCase() != validatorContract.address.toLowerCase()) { - throw Error( - `invalid address for validatorContract, expected=${initAddress[ - network.name - ].validatorContract?.toLowerCase()}, actual=${validatorContract.address.toLowerCase()}` - ); - } - console.log('All checks are done'); -}; - -deploy.tags = ['VerifyAddress']; -deploy.dependencies = ['StakingProxy', 'SlashIndicatorProxy', 'StakingVestingProxy', 'RoninValidatorSetProxy']; - -export default deploy; diff --git a/src/script/verify-address.ts b/src/script/verify-address.ts new file mode 100644 index 000000000..d8ca7c9d6 --- /dev/null +++ b/src/script/verify-address.ts @@ -0,0 +1,7 @@ +import { Address } from 'hardhat-deploy/dist/types'; + +export const verifyAddress = (actual: Address, expected?: Address) => { + if (actual.toLowerCase() != (expected || '').toLowerCase()) { + throw Error(`Invalid address, expected=${expected}, actual=${actual}`); + } +}; diff --git a/test/helpers/fixture.ts b/test/helpers/fixture.ts index e4c64cfed..fcc8ce9b0 100644 --- a/test/helpers/fixture.ts +++ b/test/helpers/fixture.ts @@ -7,6 +7,8 @@ import { MaintenanceArguments, maintenanceConf, Network, + RoninTrustedOrganizationArguments, + roninTrustedOrganizationConf, RoninValidatorSetArguments, roninValidatorSetConf, SlashIndicatorArguments, @@ -20,6 +22,7 @@ import { TransparentUpgradeableProxyV2__factory } from '../../src/types'; export interface InitTestOutput { maintenanceContractAddress: Address; + roninTrustedOrganizationAddress: Address; slashContractAddress: Address; stakingContractAddress: Address; stakingVestingContractAddress: Address; @@ -48,6 +51,8 @@ export const defaultTestConfig = { maxMaintenanceBlockPeriod: 1000, minOffset: 200, maxSchedules: 2, + + trustedOrganizations: [], }; export const initTest = (id: string) => @@ -57,7 +62,8 @@ export const initTest = (id: string) => StakingArguments & StakingVestingArguments & SlashIndicatorArguments & - RoninValidatorSetArguments & { governanceAdmin: Address } + RoninValidatorSetArguments & + RoninTrustedOrganizationArguments & { governanceAdmin: Address } >(async ({ deployments }, options) => { if (network.name == Network.Hardhat) { initAddress[network.name] = { governanceAdmin: options?.governanceAdmin ?? ethers.constants.AddressZero }; @@ -91,6 +97,9 @@ export const initTest = (id: string) => bonusPerBlock: options?.bonusPerBlock ?? defaultTestConfig.bonusPerBlock, topupAmount: options?.topupAmount ?? defaultTestConfig.topupAmount, }; + roninTrustedOrganizationConf[network.name] = { + trustedOrganizations: options?.trustedOrganizations ?? defaultTestConfig.trustedOrganizations, + }; } await deployments.fixture([ @@ -104,6 +113,7 @@ export const initTest = (id: string) => ]); const maintenanceContractDeployment = await deployments.get('MaintenanceProxy'); + const roninTrustedOrganizationDeployment = await deployments.get('RoninTrustedOrganizationProxy'); const slashContractDeployment = await deployments.get('SlashIndicatorProxy'); const stakingContractDeployment = await deployments.get('StakingProxy'); const stakingVestingContractDeployment = await deployments.get('StakingVestingProxy'); @@ -111,6 +121,7 @@ export const initTest = (id: string) => return { maintenanceContractAddress: maintenanceContractDeployment.address, + roninTrustedOrganizationAddress: roninTrustedOrganizationDeployment.address, slashContractAddress: slashContractDeployment.address, stakingContractAddress: stakingContractDeployment.address, stakingVestingContractAddress: stakingVestingContractDeployment.address, diff --git a/test/helpers/ronin-validator-set.ts b/test/helpers/ronin-validator-set.ts index 366789fbf..0061951d7 100644 --- a/test/helpers/ronin-validator-set.ts +++ b/test/helpers/ronin-validator-set.ts @@ -155,24 +155,7 @@ export const expects = { 'ValidatorSetUpdated', tx, (event) => { - expect(event.args[0], 'invalid validator set').have.deep.members(expectingValidators); - }, - 1 - ); - }, - - emitAddressesPriorityStatusUpdatedEvent: async function ( - tx: ContractTransaction, - expectingAddressList: string[], - expectingPriorityStatusList: boolean[] - ) { - await expectEvent( - contractInterface, - 'AddressesPriorityStatusUpdated', - tx, - (event) => { - expect(event.args[0], 'invalid address list').eql(expectingAddressList); - expect(event.args[1], 'invalid priority status list').eql(expectingPriorityStatusList); + expect(event.args[0], 'invalid validator set').eql(expectingValidators); }, 1 ); diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index 65144d187..c0ac9c6ca 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -61,7 +61,7 @@ describe('[Integration] Slash validators', () => { const mockValidatorLogic = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); await mockValidatorLogic.deployed(); - governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); + await governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); await network.provider.send('hardhat_mine', [ ethers.utils.hexStripZeros(BigNumber.from(numberOfBlocksInEpoch * numberOfEpochsInPeriod).toHexString()), ]); diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index f9871e00b..288ba7723 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -56,7 +56,7 @@ describe('[Integration] Submit Block Reward', () => { const mockValidatorLogic = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); await mockValidatorLogic.deployed(); - governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); + await governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); }); describe('Configuration check', async () => { diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index 2f4d391a4..73111e46b 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -56,7 +56,7 @@ describe('[Integration] Wrap up epoch', () => { const mockValidatorLogic = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); await mockValidatorLogic.deployed(); - governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); + await governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); }); after(async () => { diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index 8e58f2026..0670448e3 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -110,7 +110,7 @@ describe('Slash indicator test', () => { mockSlashLogic = await new MockSlashIndicatorExtended__factory(deployer).deploy(); await mockSlashLogic.deployed(); - governanceAdmin.upgrade(slashContractAddress, mockSlashLogic.address); + await governanceAdmin.upgrade(slashContractAddress, mockSlashLogic.address); validatorCandidates = validatorCandidates.slice(0, maxValidatorNumber); for (let i = 0; i < maxValidatorNumber; i++) { diff --git a/test/validator/RoninValidatorSet-ArrangeValidators.test.ts b/test/validator/ArrangeValidators.test.ts similarity index 72% rename from test/validator/RoninValidatorSet-ArrangeValidators.test.ts rename to test/validator/ArrangeValidators.test.ts index 4b45623a1..b8edd3f44 100644 --- a/test/validator/RoninValidatorSet-ArrangeValidators.test.ts +++ b/test/validator/ArrangeValidators.test.ts @@ -4,45 +4,41 @@ import { ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { Address } from 'hardhat-deploy/dist/types'; -import * as RoninValidatorSet from '../helpers/ronin-validator-set'; import { - Staking, MockRoninValidatorSetExtended, MockRoninValidatorSetExtended__factory, - Staking__factory, - TransparentUpgradeableProxyV2__factory, - MockSlashIndicator, - MockSlashIndicator__factory, - StakingVesting__factory, - Maintenance__factory, + MockSlashIndicatorExtended__factory, + MockSlashIndicatorExtended, + RoninTrustedOrganization__factory, + RoninTrustedOrganization, } from '../../src/types'; +import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; let validatorContract: MockRoninValidatorSetExtended; -let stakingContract: Staking; -let slashIndicator: MockSlashIndicator; +let slashIndicator: MockSlashIndicatorExtended; +let governanceAdmin: GovernanceAdminInterface; +let roninTrustedOrganization: RoninTrustedOrganization; let deployer: SignerWithAddress; -let governanceAdmin: SignerWithAddress; -let proxyAdmin: SignerWithAddress; +let governor: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; const slashFelonyAmount = 100; -const slashDoubleSignAmount = BigNumber.from(10).pow(18).mul(10); - const maxValidatorNumber = 7; const maxPrioritizedValidatorNumber = 4; -const numberOfBlocksInEpoch = 600; -const numberOfEpochsInPeriod = 48; - const maxValidatorCandidate = 100; -const minValidatorBalance = BigNumber.from(2); - -const bonusPerBlock = BigNumber.from(1); -const topUpAmount = BigNumber.from(10000); const setPriorityStatus = async (addrs: Address[], statuses: boolean[]) => { - return TransparentUpgradeableProxyV2__factory.connect(validatorContract.address, proxyAdmin).functionDelegateCall( - validatorContract.interface.encodeFunctionData('setPrioritizedAddresses', [addrs, statuses]) + const arr = statuses.map((stt, i) => ({ address: addrs[i], stt })); + const addingTrustedOrgs = arr.filter(({ stt }) => stt).map(({ address }) => address); + const removingTrustedOrgs = arr.filter(({ stt }) => !stt).map(({ address }) => address); + await governanceAdmin.functionDelegateCall( + roninTrustedOrganization.address, + roninTrustedOrganization.interface.encodeFunctionData('addTrustedOrganizations', [addingTrustedOrgs]) + ); + await governanceAdmin.functionDelegateCall( + roninTrustedOrganization.address, + roninTrustedOrganization.interface.encodeFunctionData('removeTrustedOrganizations', [removingTrustedOrgs]) ); }; @@ -71,96 +67,32 @@ const sortArrayByBoolean = (indexes: number[], statuses: boolean[]) => { }); }; -describe('Ronin Validator Set test -- Arrange validators', () => { +describe('Arrange validators', () => { before(async () => { - [deployer, proxyAdmin, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); - - const scheduleMaintenance = await new Maintenance__factory(deployer).deploy(); - const nonce = await deployer.getTransactionCount(); - const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); - const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 6 }); - - /// - /// Deploy staking mock contract - /// - - const stakingVestingLogic = await new StakingVesting__factory(deployer).deploy(); - const stakingVesting = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( - stakingVestingLogic.address, - proxyAdmin.address, - stakingVestingLogic.interface.encodeFunctionData('initialize', [bonusPerBlock, roninValidatorSetAddr]), - { value: topUpAmount } - ); - - /// - /// Deploy slash indicator contract - /// - - slashIndicator = await new MockSlashIndicator__factory(deployer).deploy( - roninValidatorSetAddr, + [deployer, governor, ...validatorCandidates] = await ethers.getSigners(); + governanceAdmin = new GovernanceAdminInterface(governor); + + const { slashContractAddress, validatorContractAddress, roninTrustedOrganizationAddress } = await initTest( + 'ArrangeValidators' + )({ + governanceAdmin: governor.address, + maxValidatorNumber, + maxValidatorCandidate, + maxPrioritizedValidatorNumber, slashFelonyAmount, - slashDoubleSignAmount - ); - await slashIndicator.deployed(); - - /// - /// Deploy validator mock contract - /// - - const validatorLogicContract = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); - await validatorLogicContract.deployed(); - - const validatorProxyContract = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( - validatorLogicContract.address, - proxyAdmin.address, - validatorLogicContract.interface.encodeFunctionData('initialize', [ - slashIndicator.address, - stakingContractAddr, - stakingVesting.address, - scheduleMaintenance.address, - maxValidatorNumber, - maxValidatorCandidate, - maxPrioritizedValidatorNumber, - numberOfBlocksInEpoch, - numberOfEpochsInPeriod, - ]) - ); - await validatorProxyContract.deployed(); - validatorContract = MockRoninValidatorSetExtended__factory.connect(validatorProxyContract.address, deployer); - - /// - /// Deploy staking contract - /// - - const stakingLogicContract = await new Staking__factory(deployer).deploy(); - await stakingLogicContract.deployed(); - - const stakingProxyContract = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( - stakingLogicContract.address, - proxyAdmin.address, - stakingLogicContract.interface.encodeFunctionData('initialize', [roninValidatorSetAddr, minValidatorBalance]) - ); - await stakingProxyContract.deployed(); - stakingContract = Staking__factory.connect(stakingProxyContract.address, deployer); - - expect(roninValidatorSetAddr.toLowerCase(), 'wrong ronin validator set contract address').eq( - validatorContract.address.toLowerCase() - ); - expect(stakingContractAddr.toLowerCase(), 'wrong staking contract address').eq( - stakingContract.address.toLowerCase() - ); - }); - - describe('ValidatorSetContract configuration', async () => { - it('Should config the maxValidatorNumber correctly', async () => { - let _maxValidatorNumber = await validatorContract.maxValidatorNumber(); - expect(_maxValidatorNumber).to.eq(maxValidatorNumber); }); - it('Should config the maxPrioritizedValidatorNumber correctly', async () => { - let _maxPrioritizedValidatorNumber = await validatorContract.maxPrioritizedValidatorNumber(); - expect(_maxPrioritizedValidatorNumber).to.eq(maxPrioritizedValidatorNumber); - }); + validatorContract = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); + slashIndicator = MockSlashIndicatorExtended__factory.connect(slashContractAddress, deployer); + roninTrustedOrganization = RoninTrustedOrganization__factory.connect(roninTrustedOrganizationAddress, deployer); + + const mockValidatorLogic = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); + await mockValidatorLogic.deployed(); + await governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); + + const mockSlashIndicator = await new MockSlashIndicatorExtended__factory(deployer).deploy(); + await mockSlashIndicator.deployed(); + await governanceAdmin.upgrade(slashIndicator.address, mockSlashIndicator.address); }); describe('Update priority list', async () => { @@ -168,51 +100,43 @@ describe('Ronin Validator Set test -- Arrange validators', () => { let addrs = validatorCandidates.slice(0, 10).map((_) => _.address); let statuses = new Array(10).fill(true); - let tx = await setPriorityStatus(addrs, statuses); - - await RoninValidatorSet.expects.emitAddressesPriorityStatusUpdatedEvent(tx, addrs, statuses); + await setPriorityStatus(addrs, statuses); }); it('Should be able to remove prioritized validators', async () => { let addrs = validatorCandidates.slice(0, 10).map((_) => _.address); let statuses = new Array(10).fill(false); - let tx = await setPriorityStatus(addrs, statuses); - await RoninValidatorSet.expects.emitAddressesPriorityStatusUpdatedEvent(tx, addrs, statuses); + await setPriorityStatus(addrs, statuses); }); it('Should be able to add and remove prioritized validators: num(add) > num(remove)', async () => { let addrs = validatorCandidates.slice(0, 10).map((_) => _.address); let statuses = new Array(10).fill(true); - let tx = await setPriorityStatus(addrs, statuses); - await RoninValidatorSet.expects.emitAddressesPriorityStatusUpdatedEvent(tx, addrs, statuses); + await setPriorityStatus(addrs, statuses); addrs = validatorCandidates.slice(4, 7).map((_) => _.address); statuses = new Array(3).fill(false); addrs.push(...validatorCandidates.slice(10, 15).map((_) => _.address)); statuses.push(...new Array(5).fill(true)); - tx = await setPriorityStatus(addrs, statuses); - await RoninValidatorSet.expects.emitAddressesPriorityStatusUpdatedEvent(tx, addrs, statuses); + await setPriorityStatus(addrs, statuses); }); it('Should be able to add and remove prioritized validators: num(add) < num(remove)', async () => { let addrs = validatorCandidates.slice(0, 15).map((_) => _.address); let statuses = new Array(15).fill(false); - let tx = await setPriorityStatus(addrs, statuses); - await RoninValidatorSet.expects.emitAddressesPriorityStatusUpdatedEvent(tx, addrs, statuses); + await setPriorityStatus(addrs, statuses); addrs = validatorCandidates.slice(0, 10).map((_) => _.address); statuses = new Array(10).fill(true); - tx = await setPriorityStatus(addrs, statuses); - await RoninValidatorSet.expects.emitAddressesPriorityStatusUpdatedEvent(tx, addrs, statuses); + await setPriorityStatus(addrs, statuses); addrs = validatorCandidates.slice(1, 8).map((_) => _.address); statuses = new Array(7).fill(false); addrs.push(...validatorCandidates.slice(10, 14).map((_) => _.address)); statuses.push(...new Array(4).fill(true)); - tx = await setPriorityStatus(addrs, statuses); - await RoninValidatorSet.expects.emitAddressesPriorityStatusUpdatedEvent(tx, addrs, statuses); + await setPriorityStatus(addrs, statuses); }); }); @@ -247,7 +171,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { inputValidatorAddrs, actualPrioritizedNumber + actualRegularNumber ); - await expect(outputValidators).eql(expectingValidatorAddrs); + expect(outputValidators).eql(expectingValidatorAddrs); }); it('Actual(prioritized) == MaxNum(prioritized); Actual(regular) > MaxNum(regular)', async () => { @@ -273,7 +197,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { inputValidatorAddrs, maxValidatorNumber ); - await expect(outputValidators).eql(expectingValidatorAddrs); + expect(outputValidators).eql(expectingValidatorAddrs); }); it('Actual(prioritized) == MaxNum(prioritized); Actual(regular) < MaxNum(regular)', async () => { @@ -299,7 +223,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { inputValidatorAddrs, actualPrioritizedNumber + actualRegularNumber ); - await expect(outputValidators).eql(expectingValidatorAddrs); + expect(outputValidators).eql(expectingValidatorAddrs); }); it('Actual(prioritized) > MaxNum(prioritized); Actual(regular) == MaxNum(regular)', async () => { @@ -325,7 +249,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { inputValidatorAddrs, maxValidatorNumber ); - await expect(outputValidators).eql(expectingValidatorAddrs); + expect(outputValidators).eql(expectingValidatorAddrs); }); it('Actual(prioritized) > MaxNum(prioritized); Actual(regular) > MaxNum(regular)', async () => { @@ -351,7 +275,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { inputValidatorAddrs, maxValidatorNumber ); - await expect(outputValidators).eql(expectingValidatorAddrs); + expect(outputValidators).eql(expectingValidatorAddrs); }); it('Actual(prioritized) > MaxNum(prioritized); Actual(regular) < MaxNum(regular)', async () => { @@ -379,7 +303,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { inputValidatorAddrs, maxValidatorNumber ); - await expect(outputValidators).eql(expectingValidatorAddrs); + expect(outputValidators).eql(expectingValidatorAddrs); }); it('Actual(prioritized) < MaxNum(prioritized); Actual(regular) == MaxNum(regular)', async () => { @@ -405,7 +329,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { inputValidatorAddrs, actualPrioritizedNumber + actualRegularNumber ); - await expect(outputValidators).eql(expectingValidatorAddrs); + expect(outputValidators).eql(expectingValidatorAddrs); }); it('Actual(prioritized) < MaxNum(prioritized); Actual(regular) > MaxNum(regular)', async () => { @@ -433,7 +357,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { inputValidatorAddrs, maxValidatorNumber ); - await expect(outputValidators).eql(expectingValidatorAddrs); + expect(outputValidators).eql(expectingValidatorAddrs); }); it('Actual(prioritized) < MaxNum(prioritized); Actual(regular) < MaxNum(regular)', async () => { @@ -459,7 +383,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { inputValidatorAddrs, actualPrioritizedNumber + actualRegularNumber ); - await expect(outputValidators).eql(expectingValidatorAddrs); + expect(outputValidators).eql(expectingValidatorAddrs); }); }); @@ -485,7 +409,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { maxValidatorNumber ); - await expect(outputValidators).eql(expectingValidatorAddrs); + expect(outputValidators).eql(expectingValidatorAddrs); }); it('Shuffled: Actual(prioritized) > MaxNum(prioritized); Actual(regular) < MaxNum(regular)', async () => { @@ -504,7 +428,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { maxValidatorNumber ); - await expect(outputValidators).eql(expectingValidatorAddrs); + expect(outputValidators).eql(expectingValidatorAddrs); }); it('Shuffled: Actual(prioritized) < MaxNum(prioritized); Actual(regular) > MaxNum(regular)', async () => { @@ -524,7 +448,7 @@ describe('Ronin Validator Set test -- Arrange validators', () => { inputValidatorAddrs, maxValidatorNumber ); - await expect(outputValidators).eql(expectingValidatorAddrs); + expect(outputValidators).eql(expectingValidatorAddrs); }); }); }); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index 08c2bb050..feff611fe 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -8,121 +8,60 @@ import { MockRoninValidatorSetExtended, MockRoninValidatorSetExtended__factory, Staking__factory, - TransparentUpgradeableProxyV2__factory, - MockSlashIndicator, - MockSlashIndicator__factory, - StakingVesting__factory, - Maintenance__factory, + MockSlashIndicatorExtended__factory, + MockSlashIndicatorExtended, } from '../../src/types'; import * as RoninValidatorSet from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../helpers/utils'; +import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; let roninValidatorSet: MockRoninValidatorSetExtended; let stakingContract: Staking; -let slashIndicator: MockSlashIndicator; +let slashIndicator: MockSlashIndicatorExtended; +let governanceAdmin: GovernanceAdminInterface; let coinbase: SignerWithAddress; let treasury: SignerWithAddress; let deployer: SignerWithAddress; -let proxyAdmin: SignerWithAddress; +let governor: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; - let currentValidatorSet: string[]; const slashFelonyAmount = 100; -const slashDoubleSignAmount = BigNumber.from(10).pow(18).mul(10); - const maxValidatorNumber = 4; -const maxPrioritizedValidatorNumber = 0; -const numberOfBlocksInEpoch = 600; -const numberOfEpochsInPeriod = 48; - const maxValidatorCandidate = 100; const minValidatorBalance = BigNumber.from(2); - const bonusPerBlock = BigNumber.from(1); -const topUpAmount = BigNumber.from(10000); describe('Ronin Validator Set test', () => { before(async () => { - [coinbase, treasury, deployer, proxyAdmin, ...validatorCandidates] = await ethers.getSigners(); + [coinbase, treasury, deployer, governor, ...validatorCandidates] = await ethers.getSigners(); + governanceAdmin = new GovernanceAdminInterface(governor); validatorCandidates = validatorCandidates.slice(0, 5); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); - const scheduleMaintenance = await new Maintenance__factory(deployer).deploy(); - const nonce = await deployer.getTransactionCount(); - const roninValidatorSetAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 4 }); - const stakingContractAddr = ethers.utils.getContractAddress({ from: deployer.address, nonce: nonce + 6 }); - - /// - /// Deploy staking mock contract - /// - - const stakingVestingLogic = await new StakingVesting__factory(deployer).deploy(); - const stakingVesting = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( - stakingVestingLogic.address, - proxyAdmin.address, - stakingVestingLogic.interface.encodeFunctionData('initialize', [bonusPerBlock, roninValidatorSetAddr]), - { value: topUpAmount } - ); - - /// - /// Deploy slash indicator contract - /// - - slashIndicator = await new MockSlashIndicator__factory(deployer).deploy( - roninValidatorSetAddr, + const { slashContractAddress, validatorContractAddress, stakingContractAddress } = await initTest( + 'RoninValidatorSet' + )({ + governanceAdmin: governor.address, + minValidatorBalance, + maxValidatorNumber, + maxValidatorCandidate, slashFelonyAmount, - slashDoubleSignAmount - ); - await slashIndicator.deployed(); - - /// - /// Deploy validator mock contract - /// - - const validatorLogicContract = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); - await validatorLogicContract.deployed(); - - const validatorProxyContract = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( - validatorLogicContract.address, - proxyAdmin.address, - validatorLogicContract.interface.encodeFunctionData('initialize', [ - slashIndicator.address, - stakingContractAddr, - stakingVesting.address, - scheduleMaintenance.address, - maxValidatorNumber, - maxValidatorCandidate, - maxPrioritizedValidatorNumber, - numberOfBlocksInEpoch, - numberOfEpochsInPeriod, - ]) - ); - await validatorProxyContract.deployed(); - roninValidatorSet = MockRoninValidatorSetExtended__factory.connect(validatorProxyContract.address, deployer); - - /// - /// Deploy staking contract - /// + bonusPerBlock, + }); - const stakingLogicContract = await new Staking__factory(deployer).deploy(); - await stakingLogicContract.deployed(); + roninValidatorSet = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); + slashIndicator = MockSlashIndicatorExtended__factory.connect(slashContractAddress, deployer); + stakingContract = Staking__factory.connect(stakingContractAddress, deployer); - const stakingProxyContract = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( - stakingLogicContract.address, - proxyAdmin.address, - stakingLogicContract.interface.encodeFunctionData('initialize', [roninValidatorSet.address, minValidatorBalance]) - ); - await stakingProxyContract.deployed(); - stakingContract = Staking__factory.connect(stakingProxyContract.address, deployer); + const mockValidatorLogic = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); + await mockValidatorLogic.deployed(); + await governanceAdmin.upgrade(roninValidatorSet.address, mockValidatorLogic.address); - expect(roninValidatorSetAddr.toLowerCase(), 'wrong ronin validator set contract address').eq( - roninValidatorSet.address.toLowerCase() - ); - expect(stakingContractAddr.toLowerCase(), 'wrong staking contract address').eq( - stakingContract.address.toLowerCase() - ); + const mockSlashIndicator = await new MockSlashIndicatorExtended__factory(deployer).deploy(); + await mockSlashIndicator.deployed(); + await governanceAdmin.upgrade(slashIndicator.address, mockSlashIndicator.address); }); after(async () => { From d3e4841abdba9da06e4c59308fafdc68f775a8e6 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 5 Oct 2022 13:56:47 +0700 Subject: [PATCH 168/190] Update method name (#25) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ba0fe74b..18480700a 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The delegator can choose the validator to stake and receive the commission rewar | `redelegate(consensusAddrSrc, consensusAddrDst, amount)` | Unstakes `amount` RON from the `consensusAddrSrc` and stake for `consensusAddrDst` | | `getRewards(consensusAddrList)` | Returns the pending rewards and the claimable rewards | | `claimRewards(consensusAddrList)` | Claims all the reward from the validators | -| `delegatePendingReward(consensusAddrList, consensusAddr)` | Claims all the reward and delegates them to the consensus address | +| `delegateRewards(consensusAddrList, consensusAddr)` | Claims all the reward and delegates them to the consensus address | ### Reward Calculation From 8c3bb3399b830d7c89e7c5bac8c1dc3a95f5c778 Mon Sep 17 00:00:00 2001 From: Bao Date: Fri, 14 Oct 2022 14:56:38 +0700 Subject: [PATCH 169/190] Fix BlockHeader and precompile call in double sign slashing (#27) * Fix nonce to uint64 * Change precompile call's parameters * Fix BlockHeader type. Fix memory to calldata. * Fix precompile calls * Fix mock contract * Fix test --- contracts/SlashIndicator.sol | 49 ++++------------- contracts/interfaces/ISlashIndicator.sol | 51 +++-------------- .../mocks/MockSlashIndicatorExtended.sol | 4 +- test/slash/SlashIndicator.test.ts | 55 +++---------------- 4 files changed, 29 insertions(+), 130 deletions(-) diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index 9d4b4ceba..a5b046d2e 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -11,7 +11,7 @@ import "./libraries/Math.sol"; contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenanceContract, Initializable { using Math for uint256; - uint8 public constant VALIDATING_DOUBLE_SIGN_PROOF_PRECOMPILE = 0x20; + uint8 public constant VALIDATING_DOUBLE_SIGN_PROOF_PRECOMPILE = 0x67; /// @dev Mapping from validator address => period index => unavailability indicator mapping(address => mapping(uint256 => uint256)) internal _unavailabilityIndicator; @@ -120,21 +120,13 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenance */ function slashDoubleSign( address _validatorAddr, - BlockHeader memory _header1, - BlockHeader memory _header2 + bytes calldata _header1, + bytes calldata _header2 ) external override onlyCoinbase oncePerBlock { if (!_shouldSlash(_validatorAddr)) { return; } - if ( - block.number > _header1.number + doubleSigningConstrainBlocks || - block.number > _header2.number + doubleSigningConstrainBlocks || - _header1.parentHash != _header2.parentHash - ) { - return; - } - if (_validateEvidence(_header1, _header2)) { uint256 _period = _validatorContract.periodOf(block.number); _unavailabilitySlashed[_validatorAddr][_period] = SlashType.DOUBLE_SIGNING; @@ -304,46 +296,27 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenance * Note: The recover process is done by pre-compiled contract. This function is marked as * virtual for implementing mocking contract for testing purpose. */ - function _validateEvidence(BlockHeader memory _header1, BlockHeader memory _header2) + function _validateEvidence(bytes calldata _header1, bytes calldata _header2) internal view virtual returns (bool _validEvidence) { - bytes memory _input = bytes.concat(_packBlockHeader(_header1), _packBlockHeader(_header2)); - uint _inputSize = _input.length; + bytes memory _payload = abi.encodeWithSignature("validatingDoubleSignProof(bytes,bytes)", _header1, _header2); + uint _payloadLength = _payload.length; uint[1] memory _output; bytes memory _revertReason = "SlashIndicator: call to precompile fails"; assembly { - if iszero(staticcall(gas(), VALIDATING_DOUBLE_SIGN_PROOF_PRECOMPILE, _input, _inputSize, _output, 0x20)) { - revert(add(32, _revertReason), mload(_revertReason)) + let _payloadStart := add(_payload, 0x20) + if iszero( + staticcall(gas(), VALIDATING_DOUBLE_SIGN_PROOF_PRECOMPILE, _payloadStart, _payloadLength, _output, 0x20) + ) { + revert(add(0x20, _revertReason), mload(_revertReason)) } } return (_output[0] != 0); } - - /** - * @dev Packing the block header struct into a single bytes. Helper function for validating - * evidence function. - */ - function _packBlockHeader(BlockHeader memory _header) private pure returns (bytes memory) { - return - abi.encode( - _header.parentHash, - _header.ommersHash, - _header.beneficiary, - _header.stateRoot, - _header.transactionsRoot, - _header.receiptsRoot, - _header.logsBloom, - _header.difficulty, - _header.number, - _header.gasLimit, - _header.gasUsed, - _header.timestamp - ); - } } diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index 2fa75ccb0..c60e9055f 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -3,6 +3,13 @@ pragma solidity ^0.8.9; interface ISlashIndicator { + enum SlashType { + UNKNOWN, + MISDEMEANOR, + FELONY, + DOUBLE_SIGNING + } + /// @dev Emitted when the validator is slashed for unavailability event UnavailabilitySlashed(address indexed validator, SlashType slashType, uint256 period); /// @dev Emitted when the thresholds updated @@ -18,46 +25,6 @@ interface ISlashIndicator { /// @dev Emiited when the block number to jail the double signing validator to is updated event DoubleSigningJailUntilBlockUpdated(uint256 doubleSigningJailUntilBlock); - enum SlashType { - UNKNOWN, - MISDEMEANOR, - FELONY, - DOUBLE_SIGNING - } - - struct BlockHeader { - /// @dev Keccak hash of the parent block - bytes32 parentHash; - /// @dev Keccak hash of the ommers block - bytes32 ommersHash; - /// @dev Beneficiary address, i.e. mining fee recipient - address beneficiary; - /// @dev Keccak hash of the root of the state trie post execution - bytes32 stateRoot; - /// @dev Keccak hash of the root of transaction trie - bytes32 transactionsRoot; - /// @dev Keccak hash of the root node of recipients in the transaction - bytes32 receiptsRoot; - /// @dev Bloom filter of two fields log address and log topic in the receipts - bytes32[256] logsBloom; - /// @dev Scalar value of the difficulty of the previous block - uint256 difficulty; - /// @dev Scalar value of the number of ancestor blocks, i.e. block height - uint64 number; - /// @dev Scalar value of the current limit of gas usage per block - uint64 gasLimit; - /// @dev Scalar value of the total gas spent of the transactions in this block - uint64 gasUsed; - /// @dev Scalar value of the output of Unix's time() - uint64 timestamp; - /// @dev The signature of the validators - bytes32 extraData; - /// @dev A 256-bit hash which, combined with the `nonce`, proves that a sufficient amount of computation has been carried out on this block - bytes32 mixHash; - /// @dev A 64-bit value which, combined with the `mixHash`, proves that a sufficient amount of computation has been carried out on this block - uint8 nonce; - } - /** * @dev Slashes for unavailability by increasing the counter of validator with `_valAddr`. * If the counter passes the threshold, call the function from the validator contract. @@ -80,8 +47,8 @@ interface ISlashIndicator { */ function slashDoubleSign( address _validatorAddr, - BlockHeader calldata _header1, - BlockHeader calldata _header2 + bytes calldata _header1, + bytes calldata _header2 ) external; /** diff --git a/contracts/mocks/MockSlashIndicatorExtended.sol b/contracts/mocks/MockSlashIndicatorExtended.sol index b8efec658..2dccaeb9a 100644 --- a/contracts/mocks/MockSlashIndicatorExtended.sol +++ b/contracts/mocks/MockSlashIndicatorExtended.sol @@ -14,8 +14,8 @@ contract MockSlashIndicatorExtended is SlashIndicator { } function _validateEvidence( - BlockHeader memory, /*_header1*/ - BlockHeader memory /*_header2*/ + bytes calldata, /*_header1*/ + bytes calldata /*_header2*/ ) internal pure override returns (bool _validEvidence) { return true; } diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index 0670448e3..57a44e2ae 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -1,4 +1,4 @@ -import { BigNumber } from 'ethers'; +import { BigNumber, BytesLike } from 'ethers'; import { expect } from 'chai'; import { ethers, network } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; @@ -11,7 +11,6 @@ import { Staking, Staking__factory, } from '../../src/types'; -import { BlockHeaderStruct } from '../../src/types/ISlashIndicator'; import { SlashType } from '../../src/script/slash-indicator'; import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; import { EpochController } from '../helpers/ronin-validator-set'; @@ -64,26 +63,6 @@ const validateIndicatorAt = async (idx: number) => { ); }; -const generateDefaultBlockHeader = (blockHeight: number): BlockHeaderStruct => { - return { - parentHash: ethers.constants.HashZero, - ommersHash: ethers.constants.HashZero, - beneficiary: ethers.constants.AddressZero, - stateRoot: ethers.constants.HashZero, - transactionsRoot: ethers.constants.HashZero, - receiptsRoot: ethers.constants.HashZero, - logsBloom: new Array(256).fill(ethers.constants.HashZero), - difficulty: 1, - number: blockHeight, - gasLimit: 1, - gasUsed: 1, - timestamp: 1, - extraData: ethers.constants.HashZero, - mixHash: ethers.constants.HashZero, - nonce: 1, - }; -}; - describe('Slash indicator test', () => { before(async () => { [deployer, coinbase, governor, vagabond, ...validatorCandidates] = await ethers.getSigners(); @@ -340,8 +319,8 @@ describe('Slash indicator test', () => { }); describe('Double signing slash', async () => { - let header1: BlockHeaderStruct; - let header2: BlockHeaderStruct; + let header1: BytesLike; + let header2: BytesLike; before(async () => { await network.provider.send('hardhat_mine', [doubleSigningConstrainBlocks.toHexString()]); @@ -351,10 +330,8 @@ describe('Slash indicator test', () => { const slasherIdx = 0; await network.provider.send('hardhat_setCoinbase', [validatorCandidates[slasherIdx].address]); - let nextBlockHeight = await network.provider.send('eth_blockNumber'); - - header1 = generateDefaultBlockHeader(nextBlockHeight - 1); - header2 = generateDefaultBlockHeader(nextBlockHeight - 1); + header1 = ethers.utils.toUtf8Bytes('sampleHeader1'); + header2 = ethers.utils.toUtf8Bytes('sampleHeader2'); let tx = await slashContract .connect(validatorCandidates[slasherIdx]) @@ -363,30 +340,12 @@ describe('Slash indicator test', () => { await expect(tx).to.not.emit(slashContract, 'UnavailabilitySlashed'); }); - it('Should not be able to slash with mismatched parent hash', async () => { - const slasherIdx = 0; - const slasheeIdx = 1; - let nextBlockHeight = await network.provider.send('eth_blockNumber'); - - header1 = generateDefaultBlockHeader(nextBlockHeight - 1); - header2 = generateDefaultBlockHeader(nextBlockHeight - 1); - - header1.parentHash = ethers.constants.HashZero.slice(0, -1) + '1'; - - let tx = await slashContract - .connect(validatorCandidates[slasherIdx]) - .slashDoubleSign(validatorCandidates[slasheeIdx].address, header1, header2); - - await expect(tx).to.not.emit(slashContract, 'UnavailabilitySlashed'); - }); - it('Should be able to slash validator with double signing', async () => { const slasherIdx = 0; const slasheeIdx = 1; - let nextBlockHeight = await network.provider.send('eth_blockNumber'); - header1 = generateDefaultBlockHeader(nextBlockHeight - 1); - header2 = generateDefaultBlockHeader(nextBlockHeight - 1); + header1 = ethers.utils.toUtf8Bytes('sampleHeader1'); + header2 = ethers.utils.toUtf8Bytes('sampleHeader2'); let tx = await slashContract .connect(validatorCandidates[slasherIdx]) From 4ed91d5da03b17fbf96a00e6d23ed452cfa82d51 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Sat, 15 Oct 2022 08:22:21 +0700 Subject: [PATCH 170/190] [Staking] Fix reward calculation (#24) * Fix reward calculation * Remove the `balance` variable --- contracts/interfaces/IRewardPool.sol | 10 +- contracts/mocks/MockStaking.sol | 2 +- contracts/staking/RewardCalculation.sol | 26 +- ...on.test.ts => AdvancedCalculation.test.ts} | 77 ++--- test/staking/RewardAmountCalculation.test.ts | 327 ++++++++++++++++++ 5 files changed, 381 insertions(+), 61 deletions(-) rename test/staking/{RewardCalculation.test.ts => AdvancedCalculation.test.ts} (90%) create mode 100644 test/staking/RewardAmountCalculation.test.ts diff --git a/contracts/interfaces/IRewardPool.sol b/contracts/interfaces/IRewardPool.sol index d0b68e3c5..9ced24224 100644 --- a/contracts/interfaces/IRewardPool.sol +++ b/contracts/interfaces/IRewardPool.sol @@ -8,13 +8,7 @@ interface IRewardPool { /// @dev Emitted when the pending pool is updated. event PendingPoolUpdated(address poolAddress, uint256 accumulatedRps); /// @dev Emitted when the fields to calculate settled reward for the user is updated. - event SettledRewardUpdated( - address poolAddress, - address user, - uint256 balance, - uint256 debited, - uint256 accumulatedRps - ); + event SettledRewardUpdated(address poolAddress, address user, uint256 debited, uint256 accumulatedRps); /// @dev Emitted when the fields to calculate pending reward for the user is updated. event PendingRewardUpdated(address poolAddress, address user, uint256 debited, uint256 credited); /// @dev Emitted when the user claimed their reward @@ -30,8 +24,6 @@ interface IRewardPool { } struct SettledRewardFields { - // The balance at the commit time. - uint256 balance; // Recorded reward amount. uint256 debited; // Accumulated of the amount rewards per share (one unit staking). diff --git a/contracts/mocks/MockStaking.sol b/contracts/mocks/MockStaking.sol index bdd9a7612..f656b0425 100644 --- a/contracts/mocks/MockStaking.sol +++ b/contracts/mocks/MockStaking.sol @@ -49,7 +49,7 @@ contract MockStaking is RewardCalculation { _recordReward(poolAddr, _rewardAmount); } - function commitRewardPool(address[] calldata _addrList) external { + function settledPools(address[] calldata _addrList) external { _onPoolsSettled(_addrList); } diff --git a/contracts/staking/RewardCalculation.sol b/contracts/staking/RewardCalculation.sol index 0451665ed..8aac6e366 100644 --- a/contracts/staking/RewardCalculation.sol +++ b/contracts/staking/RewardCalculation.sol @@ -29,8 +29,8 @@ abstract contract RewardCalculation is IRewardPool { uint256 _balance = balanceOf(_poolAddr, _user); if (_rewardSinked(_poolAddr, _periodOf(_reward.lastSyncedBlock))) { SettledRewardFields memory _sReward = _sUserReward[_poolAddr][_user]; - uint256 _credited = (_sReward.accumulatedRps * _balance) / 1e18; - return (_balance * _pool.accumulatedRps) / 1e18 + _sReward.debited - _credited; + uint256 _diffRps = _pool.accumulatedRps - _sReward.accumulatedRps; + return (_balance * _diffRps) / 1e18 + _sReward.debited; } return (_balance * _pool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; @@ -44,13 +44,18 @@ abstract contract RewardCalculation is IRewardPool { SettledRewardFields memory _sReward = _sUserReward[_poolAddr][_user]; SettledPool memory _sPool = _settledPool[_poolAddr]; + uint256 _diffRps = _sPool.accumulatedRps - _sReward.accumulatedRps; if (_reward.lastSyncedBlock <= _sPool.lastSyncedBlock) { uint256 _currentBalance = balanceOf(_poolAddr, _user); + + if (_rewardSinked(_poolAddr, _periodOf(_reward.lastSyncedBlock))) { + return (_currentBalance * _diffRps) / 1e18 + _sReward.debited; + } + return (_currentBalance * _sPool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; } - uint256 _diffRps = _sPool.accumulatedRps - _sReward.accumulatedRps; - return (_sReward.balance * _diffRps) / 1e18 + _sReward.debited; + return _sReward.debited; } /** @@ -90,13 +95,11 @@ abstract contract RewardCalculation is IRewardPool { // Syncs the reward once the last sync is settled. if (_reward.lastSyncedBlock <= _sPool.lastSyncedBlock) { uint256 _claimableReward = getClaimableReward(_poolAddr, _user); - uint256 _balance = balanceOf(_poolAddr, _user); SettledRewardFields storage _sReward = _sUserReward[_poolAddr][_user]; - _sReward.balance = _balance; _sReward.debited = _claimableReward; _sReward.accumulatedRps = _sPool.accumulatedRps; - emit SettledRewardUpdated(_poolAddr, _user, _balance, _claimableReward, _sPool.accumulatedRps); + emit SettledRewardUpdated(_poolAddr, _user, _claimableReward, _sPool.accumulatedRps); } PendingPool memory _pool = _pendingPool[_poolAddr]; @@ -127,10 +130,9 @@ abstract contract RewardCalculation is IRewardPool { _sReward.debited = 0; if (_reward.lastSyncedBlock <= _sPool.lastSyncedBlock) { - _sReward.balance = balanceOf(_poolAddr, _user); _sReward.accumulatedRps = _sPool.accumulatedRps; } - emit SettledRewardUpdated(_poolAddr, _user, _sReward.balance, 0, _sReward.accumulatedRps); + emit SettledRewardUpdated(_poolAddr, _user, 0, _sReward.accumulatedRps); _reward.credited += _amount; _reward.lastSyncedBlock = block.number; @@ -183,8 +185,10 @@ abstract contract RewardCalculation is IRewardPool { _accumulatedRpsList[_i] = _pendingPool[_poolAddr].accumulatedRps; SettledPool storage _sPool = _settledPool[_poolAddr]; - _sPool.accumulatedRps = _accumulatedRpsList[_i]; - _sPool.lastSyncedBlock = block.number; + if (_accumulatedRpsList[_i] != _sPool.accumulatedRps) { + _sPool.accumulatedRps = _accumulatedRpsList[_i]; + _sPool.lastSyncedBlock = block.number; + } } emit SettledPoolsUpdated(_poolList, _accumulatedRpsList); } diff --git a/test/staking/RewardCalculation.test.ts b/test/staking/AdvancedCalculation.test.ts similarity index 90% rename from test/staking/RewardCalculation.test.ts rename to test/staking/AdvancedCalculation.test.ts index 7efd74adf..102317633 100644 --- a/test/staking/RewardCalculation.test.ts +++ b/test/staking/AdvancedCalculation.test.ts @@ -39,7 +39,7 @@ const local = { ); this.aRps = this.aRps.add(BigNumber.from(reward).mul(MASK).div(totalStaked)); }, - commitRewardPool: function () { + settledPools: function () { this.claimableRewardForA = this.accumulatedRewardForA; this.claimableRewardForB = this.accumulatedRewardForB; this.settledARps = this.aRps; @@ -96,7 +96,7 @@ const expectLocalCalculationRight = async () => { } }; -describe('Reward Calculation test', () => { +describe('Advanced Calculation test', () => { let tx: ContractTransaction; const txs: ContractTransaction[] = []; @@ -111,7 +111,7 @@ describe('Reward Calculation test', () => { await network.provider.send('evm_setAutomine', [true]); }); - it('Should work properly with staking actions occurring sequentially for a normal period', async () => { + it('Should work properly with staking actions occurring sequentially for a period that will be settled', async () => { txs[0] = await stakingContract.stake(userA.address, 100); txs[1] = await stakingContract.stake(userB.address, 100); await network.provider.send('evm_mine'); @@ -165,21 +165,21 @@ describe('Reward Calculation test', () => { .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK)); await expectLocalCalculationRight(); - tx = await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + tx = await stakingContract.settledPools([poolAddr]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await StakingContract.expects.emitSettledPoolsUpdatedEvent(tx!, [poolAddr], [local.aRps]); - local.commitRewardPool(); + local.settledPools(); await expectLocalCalculationRight(); }); - it('Should work properly with staking actions occurring sequentially for a slashed period', async () => { + it('Should work properly with staking actions occurring sequentially for a period that will be slashed', async () => { txs[0] = await stakingContract.stake(userA.address, 100); await network.provider.send('evm_mine'); await expectLocalCalculationRight(); await expect(txs[0]!) .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userA.address, local.balanceA, 2250, local.aRps); + .withArgs(poolAddr, userA.address, 2250, local.aRps); await local.syncBalance(); await expect(txs[0]!) .emit(stakingContract, 'PendingRewardUpdated') @@ -231,7 +231,7 @@ describe('Reward Calculation test', () => { await expect(txs[0]!) .emit(stakingContract, 'PendingRewardUpdated') .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK)); - txs[0] = await stakingContract.unstake(userA.address, 100); + txs[0] = await stakingContract.unstake(userA.address, 50); await network.provider.send('evm_mine'); await expectLocalCalculationRight(); await local.syncBalance(); @@ -239,19 +239,16 @@ describe('Reward Calculation test', () => { .emit(stakingContract, 'PendingRewardUpdated') .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK)); - tx = await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + tx = await stakingContract.settledPools([poolAddr]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await expectLocalCalculationRight(); await StakingContract.expects.emitSettledPoolsUpdatedEvent(tx!, [poolAddr], [local.aRps]); }); - it('Should work properly with staking actions occurring sequentially for a slashed period again', async () => { - txs[0] = await stakingContract.stake(userA.address, 100); + it('Should work properly with staking actions occurring sequentially for a period that will be slashed again', async () => { + txs[0] = await stakingContract.stake(userA.address, 50); await network.provider.send('evm_mine'); - await expect(txs[0]!) - .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userA.address, local.balanceA, local.claimableRewardForA, local.settledARps); await local.syncBalance(); await expect(txs[0]!) .emit(stakingContract, 'PendingRewardUpdated') @@ -269,7 +266,7 @@ describe('Reward Calculation test', () => { .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK).add(local.claimableRewardForA)); await expect(txs[0]!) .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userA.address, 300, 0, local.settledARps); + .withArgs(poolAddr, userA.address, 0, local.settledARps); local.claimRewardForA(); await local.recordReward(1000); await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); @@ -325,7 +322,7 @@ describe('Reward Calculation test', () => { await expectLocalCalculationRight(); txs[1] = await stakingContract.claimReward(userB.address); - tx = await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + tx = await stakingContract.settledPools([poolAddr]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await expect(txs[1]!) @@ -336,10 +333,10 @@ describe('Reward Calculation test', () => { .withArgs(poolAddr, userB.address, 0, local.aRps.mul(local.balanceB).div(MASK)); await expect(txs[1]!) .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userB.address, local.balanceB, 0, local.settledARps); + .withArgs(poolAddr, userB.address, 0, local.settledARps); local.claimRewardForB(); await StakingContract.expects.emitSettledPoolsUpdatedEvent(tx!, [poolAddr], [local.aRps]); - local.commitRewardPool(); + local.settledPools(); await expectLocalCalculationRight(); }); @@ -347,13 +344,13 @@ describe('Reward Calculation test', () => { const lastCredited = local.aRps.mul(300).div(MASK); txs[0] = await stakingContract.recordReward(1000); - txs[1] = await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + txs[1] = await stakingContract.settledPools([poolAddr]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await local.recordReward(1000); await expect(txs[0]!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await StakingContract.expects.emitSettledPoolsUpdatedEvent(txs[1]!, [poolAddr], [local.aRps]); - local.commitRewardPool(); + local.settledPools(); await expectLocalCalculationRight(); txs[0] = await stakingContract.claimReward(userA.address); @@ -367,7 +364,7 @@ describe('Reward Calculation test', () => { .withArgs(poolAddr, userA.address, 0, lastCredited.add(local.claimableRewardForA)); await expect(txs[0]!) .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userA.address, 300, 0, local.aRps); + .withArgs(poolAddr, userA.address, 0, local.aRps); local.claimRewardForA(); await local.recordReward(1000); await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); @@ -383,10 +380,10 @@ describe('Reward Calculation test', () => { .withArgs(poolAddr, userA.address, 0, lastCredited.add(750)); await expect(txs[0]!) .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userA.address, 300, 0, local.settledARps); + .withArgs(poolAddr, userA.address, 0, local.settledARps); txs[1] = await stakingContract.claimReward(userB.address); - tx = await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + tx = await stakingContract.settledPools([poolAddr]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await expect(txs[1]!).emit(stakingContract, 'RewardClaimed').withArgs(poolAddr, userB.address, 250); @@ -395,9 +392,9 @@ describe('Reward Calculation test', () => { .withArgs(poolAddr, userB.address, 0, 1750 + 250); await expect(txs[1]!) .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userB.address, 100, 0, local.settledARps); + .withArgs(poolAddr, userB.address, 0, local.settledARps); local.claimRewardForB(); - local.commitRewardPool(); + local.settledPools(); await StakingContract.expects.emitSettledPoolsUpdatedEvent(tx!, [poolAddr], [local.settledARps]); await expectLocalCalculationRight(); }); @@ -407,7 +404,7 @@ describe('Reward Calculation test', () => { await network.provider.send('evm_mine'); await expect(txs[0]!) .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userA.address, local.balanceA, local.claimableRewardForA, local.settledARps); + .withArgs(poolAddr, userA.address, local.claimableRewardForA, local.settledARps); await local.syncBalance(); await expect(txs[0]!) .emit(stakingContract, 'PendingRewardUpdated') @@ -429,7 +426,7 @@ describe('Reward Calculation test', () => { await network.provider.send('evm_mine'); await expect(txs[1]!) .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userB.address, local.balanceB, local.claimableRewardForB, local.settledARps); + .withArgs(poolAddr, userB.address, local.claimableRewardForB, local.settledARps); await local.recordReward(1000); await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); await expectLocalCalculationRight(); @@ -472,7 +469,7 @@ describe('Reward Calculation test', () => { .withArgs(poolAddr, userA.address, local.claimableRewardForA); await expect(txs[0]!) .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userA.address, local.balanceA, 0, local.settledARps); + .withArgs(poolAddr, userA.address, 0, local.settledARps); await expect(txs[0]!) .emit(stakingContract, 'PendingRewardUpdated') .withArgs(poolAddr, userA.address, 3725, lastCreditedA); @@ -482,7 +479,7 @@ describe('Reward Calculation test', () => { .withArgs(poolAddr, userB.address, local.claimableRewardForB); await expect(txs[1]!) .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userB.address, local.balanceB, 0, local.settledARps); + .withArgs(poolAddr, userB.address, 0, local.settledARps); await expect(txs[1]!) .emit(stakingContract, 'PendingRewardUpdated') .withArgs(poolAddr, userB.address, 1275, lastCreditedB); @@ -491,7 +488,7 @@ describe('Reward Calculation test', () => { await expectLocalCalculationRight(); txs[0] = await stakingContract.unstake(userA.address, 200); - tx = await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + tx = await stakingContract.settledPools([poolAddr]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await local.syncBalance(); @@ -499,7 +496,7 @@ describe('Reward Calculation test', () => { await expect(txs[0]!) .emit(stakingContract, 'PendingRewardUpdated') .withArgs(poolAddr, userA.address, local.accumulatedRewardForA, lastCreditedA); - local.commitRewardPool(); + local.settledPools(); await StakingContract.expects.emitSettledPoolsUpdatedEvent(tx!, [poolAddr], [local.settledARps]); await expectLocalCalculationRight(); @@ -512,7 +509,7 @@ describe('Reward Calculation test', () => { .withArgs(poolAddr, userA.address, local.claimableRewardForA); await expect(txs[0]!) .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userA.address, local.balanceA, 0, local.settledARps); + .withArgs(poolAddr, userA.address, 0, local.settledARps); await expect(txs[0]!) .emit(stakingContract, 'PendingRewardUpdated') .withArgs(poolAddr, userA.address, 3725, lastCreditedA); @@ -522,7 +519,7 @@ describe('Reward Calculation test', () => { .withArgs(poolAddr, userB.address, local.claimableRewardForB); await expect(txs[1]!) .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userB.address, local.balanceB, 0, local.settledARps); + .withArgs(poolAddr, userB.address, 0, local.settledARps); await expect(txs[1]!) .emit(stakingContract, 'PendingRewardUpdated') .withArgs(poolAddr, userB.address, 1275, lastCreditedB); @@ -538,11 +535,11 @@ describe('Reward Calculation test', () => { await stakingContract.stake(userB.address, 200); await stakingContract.unstake(userB.address, 200); await stakingContract.recordReward(1000); - await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + await stakingContract.settledPools([poolAddr]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await local.recordReward(1000); - local.commitRewardPool(); + local.settledPools(); await expectLocalCalculationRight(); await stakingContract.recordReward(1000); @@ -555,25 +552,25 @@ describe('Reward Calculation test', () => { local.slash(); await expectLocalCalculationRight(); - await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + await stakingContract.settledPools([poolAddr]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await expectLocalCalculationRight(); await stakingContract.recordReward(1000); - await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + await stakingContract.settledPools([poolAddr]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await local.recordReward(1000); - local.commitRewardPool(); + local.settledPools(); await expectLocalCalculationRight(); await stakingContract.recordReward(1000); - await stakingContract.commitRewardPool([ethers.constants.AddressZero]); + await stakingContract.settledPools([poolAddr]); await stakingContract.endPeriod(); await network.provider.send('evm_mine'); await local.recordReward(1000); - local.commitRewardPool(); + local.settledPools(); await expectLocalCalculationRight(); await stakingContract.claimReward(userA.address); diff --git a/test/staking/RewardAmountCalculation.test.ts b/test/staking/RewardAmountCalculation.test.ts new file mode 100644 index 000000000..04bddebae --- /dev/null +++ b/test/staking/RewardAmountCalculation.test.ts @@ -0,0 +1,327 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; + +import { MockStaking, MockStaking__factory } from '../../src/types'; + +const poolAddr = ethers.constants.AddressZero; + +let deployer: SignerWithAddress; +let userA: SignerWithAddress; +let userB: SignerWithAddress; +let stakingContract: MockStaking; + +const setupNormalCase = async (stakingContract: MockStaking) => { + await stakingContract.stake(userA.address, 100); + await stakingContract.stake(userB.address, 100); + await stakingContract.recordReward(1000); + await stakingContract.settledPools([poolAddr]); + await stakingContract.endPeriod(); +}; + +const expectPendingRewards = async (expectingA: number, expectingB: number) => { + expect(await stakingContract.getPendingReward(poolAddr, userA.address)).eq(expectingA); + expect(await stakingContract.getPendingReward(poolAddr, userB.address)).eq(expectingB); +}; + +const expectClaimableRewards = async (expectingA: number, expectingB: number) => { + expect(await stakingContract.getClaimableReward(poolAddr, userA.address)).eq(expectingA); + expect(await stakingContract.getClaimableReward(poolAddr, userB.address)).eq(expectingB); +}; + +describe('Claimable/Pending Reward Calculation test', () => { + before(async () => { + [deployer, userA, userB] = await ethers.getSigners(); + stakingContract = await new MockStaking__factory(deployer).deploy(poolAddr); + }); + + it('Should calculate correctly the claimable reward in the normal case', async () => { + await setupNormalCase(stakingContract); + await expectClaimableRewards(500, 500); + await expectPendingRewards(0, 0); + }); + + describe('Interaction with a pool that will be settled', async () => { + describe('One interaction per period', async () => { + before(async () => { + stakingContract = await new MockStaking__factory(deployer).deploy(poolAddr); + await setupNormalCase(stakingContract); + }); + + it('Should the claimable reward not change when the user interacts in the pending period', async () => { + await stakingContract.stake(userA.address, 200); + await stakingContract.recordReward(1000); + await expectClaimableRewards(500, 500); + await expectPendingRewards(750, 250); + }); + + it('Should the claimable reward increase when the pool is settled', async () => { + await stakingContract.settledPools([poolAddr]); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 750, 500 + 250); + await expectPendingRewards(0, 0); + }); + + it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 750, 500 + 250); + await expectPendingRewards(0, 0); + await stakingContract.settledPools([poolAddr]); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 750, 500 + 250); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 750, 500 + 250); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 750, 500 + 250); + await expectPendingRewards(0, 0); + }); + + it('Should the claimable reward increase when the pool is settled', async () => { + await stakingContract.recordReward(1000); + await expectPendingRewards(750, 250); + await stakingContract.settledPools([poolAddr]); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 750 + 750, 500 + 250 + 250); + await expectPendingRewards(0, 0); + }); + + it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 750 + 750, 500 + 250 + 250); + await expectPendingRewards(0, 0); + await stakingContract.settledPools([poolAddr]); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 750 + 750, 500 + 250 + 250); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 750 + 750, 500 + 250 + 250); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 750 + 750, 500 + 250 + 250); + await expectPendingRewards(0, 0); + }); + }); + + describe('Many interactions per period', async () => { + before(async () => { + stakingContract = await new MockStaking__factory(deployer).deploy(poolAddr); + await setupNormalCase(stakingContract); + }); + + it('Should the claimable reward not change when the user interacts in the pending period', async () => { + await stakingContract.stake(userA.address, 200); + await stakingContract.stake(userA.address, 500); + await stakingContract.stake(userA.address, 100); + await stakingContract.recordReward(1000); + await stakingContract.stake(userA.address, 800); + await expectClaimableRewards(500, 500); + await expectPendingRewards(900, 100); + }); + + it('Should the claimable reward increase when the pool is settled', async () => { + await stakingContract.settledPools([poolAddr]); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900, 500 + 100); + }); + + it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { + await stakingContract.slash(); + await stakingContract.unstake(userA.address, 800); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900, 500 + 100); + await expectPendingRewards(0, 0); + await stakingContract.settledPools([poolAddr]); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900, 500 + 100); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900, 500 + 100); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900, 500 + 100); + await expectPendingRewards(0, 0); + }); + + it('Should the claimable reward increase when the pool is settled', async () => { + await stakingContract.recordReward(1000); + await expectPendingRewards(900, 100); + await stakingContract.settledPools([poolAddr]); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900 + 900, 500 + 100 + 100); + await expectPendingRewards(0, 0); + }); + + it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900 + 900, 500 + 100 + 100); + await expectPendingRewards(0, 0); + await stakingContract.settledPools([poolAddr]); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900 + 900, 500 + 100 + 100); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900 + 900, 500 + 100 + 100); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900 + 900, 500 + 100 + 100); + await expectPendingRewards(0, 0); + }); + }); + }); + + describe('Interaction with a pool that will be slashed', async () => { + describe('One interaction per period', async () => { + before(async () => { + stakingContract = await new MockStaking__factory(deployer).deploy(poolAddr); + await setupNormalCase(stakingContract); + }); + + it('Should the claimable reward not change when the user interacts in the pending period', async () => { + await stakingContract.stake(userA.address, 200); + await stakingContract.recordReward(1000); + await stakingContract.stake(userA.address, 600); + await expectClaimableRewards(500, 500); + await expectPendingRewards(750, 250); + }); + + it('Should the claimable reward not change when the pool is slashed', async () => { + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500, 500); + await expectPendingRewards(0, 0); + }); + + it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500, 500); + await expectPendingRewards(0, 0); + await stakingContract.settledPools([poolAddr]); + await stakingContract.endPeriod(); + await expectClaimableRewards(500, 500); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500, 500); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500, 500); + await expectPendingRewards(0, 0); + }); + + it('Should the claimable reward increase when the pool records reward', async () => { + await stakingContract.recordReward(1000); + await expectPendingRewards(900, 100); + await stakingContract.settledPools([poolAddr]); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900, 500 + 100); + await expectPendingRewards(0, 0); + }); + + it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900, 500 + 100); + await expectPendingRewards(0, 0); + await stakingContract.settledPools([poolAddr]); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900, 500 + 100); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900, 500 + 100); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900, 500 + 100); + await expectPendingRewards(0, 0); + }); + }); + + describe('Many interactions per period', async () => { + before(async () => { + stakingContract = await new MockStaking__factory(deployer).deploy(poolAddr); + await setupNormalCase(stakingContract); + }); + + it('Should the claimable reward not change when the user interacts in the pending period', async () => { + await stakingContract.stake(userA.address, 200); + await stakingContract.unstake(userA.address, 150); + await stakingContract.unstake(userA.address, 50); + await stakingContract.recordReward(1000); + await stakingContract.stake(userA.address, 500); + await stakingContract.stake(userA.address, 300); + await expectClaimableRewards(500, 500); + await expectPendingRewards(500, 500); + }); + + it('Should the claimable reward not change when the pool is slashed', async () => { + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500, 500); + await expectPendingRewards(0, 0); + }); + + it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500, 500); + await expectPendingRewards(0, 0); + await stakingContract.settledPools([poolAddr]); + await stakingContract.endPeriod(); + await expectClaimableRewards(500, 500); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500, 500); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500, 500); + await expectPendingRewards(0, 0); + }); + + it('Should the claimable reward increase when the pool records reward', async () => { + await stakingContract.recordReward(1000); + await expectPendingRewards(900, 100); + await stakingContract.settledPools([poolAddr]); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900, 500 + 100); + await expectPendingRewards(0, 0); + }); + + it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900, 500 + 100); + await expectPendingRewards(0, 0); + await stakingContract.settledPools([poolAddr]); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900, 500 + 100); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900, 500 + 100); + await expectPendingRewards(0, 0); + await stakingContract.slash(); + await stakingContract.endPeriod(); + await expectClaimableRewards(500 + 900, 500 + 100); + await expectPendingRewards(0, 0); + }); + }); + }); +}); From 30369233b3298d07aff08ef7d2e1a849735150e0 Mon Sep 17 00:00:00 2001 From: Bao Date: Mon, 17 Oct 2022 10:59:20 +0700 Subject: [PATCH 171/190] Use precompile for sorting validators. Add assembly test of precompiles. (#28) * Add sample sorting precompile contract * Call sorting candidates by precompile * Fix test script * Clean up * Add test for precompile validating double sign * Fix test suite name * Refactor precompile sorting validators * Refactor precompile validating double sign proof * Update revert reason for precompile * Generalize test. Rename mock contract. * Handle precompile call fails * Refactor --- contracts/SlashIndicator.sol | 51 ++++++----------- contracts/mocks/MockPrecompile.sol | 22 +++++++ .../mocks/MockRoninValidatorSetExtended.sol | 10 ++++ .../mocks/MockRoninValidatorSetSorting.sol | 19 +++++++ .../MockPrecompileUsageSortValidators.sol | 29 ++++++++++ .../MockPrecompileUsageValidateDoubleSign.sol | 25 ++++++++ .../PrecompileUsageSortValidators.sol | 43 ++++++++++++++ .../PrecompileUsageValidateDoubleSign.sol | 42 ++++++++++++++ contracts/validator/RoninValidatorSet.sol | 14 ++++- test/maintainance/Maintenance.test.ts | 17 ++++-- .../PrecompileUsageSortValidators.test.ts | 57 +++++++++++++++++++ .../PrecompileUsageValidateDoubleSign.test.ts | 45 +++++++++++++++ test/slash/SlashIndicator.test.ts | 8 ++- 13 files changed, 340 insertions(+), 42 deletions(-) create mode 100644 contracts/mocks/MockPrecompile.sol create mode 100644 contracts/mocks/MockRoninValidatorSetSorting.sol create mode 100644 contracts/mocks/precompile-usages/MockPrecompileUsageSortValidators.sol create mode 100644 contracts/mocks/precompile-usages/MockPrecompileUsageValidateDoubleSign.sol create mode 100644 contracts/precompile-usages/PrecompileUsageSortValidators.sol create mode 100644 contracts/precompile-usages/PrecompileUsageValidateDoubleSign.sol create mode 100644 test/precompile-usages/PrecompileUsageSortValidators.test.ts create mode 100644 test/precompile-usages/PrecompileUsageValidateDoubleSign.test.ts diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index a5b046d2e..8ed25e216 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -7,11 +7,19 @@ import "./interfaces/ISlashIndicator.sol"; import "./extensions/HasValidatorContract.sol"; import "./extensions/HasMaintenanceContract.sol"; import "./libraries/Math.sol"; - -contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenanceContract, Initializable { +import "./precompile-usages/PrecompileUsageValidateDoubleSign.sol"; + +contract SlashIndicator is + ISlashIndicator, + PrecompileUsageValidateDoubleSign, + HasValidatorContract, + HasMaintenanceContract, + Initializable +{ using Math for uint256; - uint8 public constant VALIDATING_DOUBLE_SIGN_PROOF_PRECOMPILE = 0x67; + /// @dev The address of the precompile of sorting validators + address internal constant _precompileValidateDoubleSignAddress = address(0x67); /// @dev Mapping from validator address => period index => unavailability indicator mapping(address => mapping(uint256 => uint256)) internal _unavailabilityIndicator; @@ -227,6 +235,13 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenance return _unavailabilityIndicator[_validator][_period]; } + /** + * @inheritdoc PrecompileUsageValidateDoubleSign + */ + function precompileValidateDoubleSignAddress() public pure override returns (address) { + return _precompileValidateDoubleSignAddress; + } + /////////////////////////////////////////////////////////////////////////////////////// // HELPER FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// @@ -289,34 +304,4 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenance _validatorContract.isValidator(_addr) && !_maintenanceContract.maintaining(_addr, block.number); } - - /** - * @dev Validate the two submitted block header if they are produced by the same address - * - * Note: The recover process is done by pre-compiled contract. This function is marked as - * virtual for implementing mocking contract for testing purpose. - */ - function _validateEvidence(bytes calldata _header1, bytes calldata _header2) - internal - view - virtual - returns (bool _validEvidence) - { - bytes memory _payload = abi.encodeWithSignature("validatingDoubleSignProof(bytes,bytes)", _header1, _header2); - uint _payloadLength = _payload.length; - uint[1] memory _output; - - bytes memory _revertReason = "SlashIndicator: call to precompile fails"; - - assembly { - let _payloadStart := add(_payload, 0x20) - if iszero( - staticcall(gas(), VALIDATING_DOUBLE_SIGN_PROOF_PRECOMPILE, _payloadStart, _payloadLength, _output, 0x20) - ) { - revert(add(0x20, _revertReason), mload(_revertReason)) - } - } - - return (_output[0] != 0); - } } diff --git a/contracts/mocks/MockPrecompile.sol b/contracts/mocks/MockPrecompile.sol new file mode 100644 index 000000000..fac64d784 --- /dev/null +++ b/contracts/mocks/MockPrecompile.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../libraries/Sorting.sol"; + +contract MockPrecompile { + function sortValidators(address[] memory _validators, uint256[] memory _weights) + external + pure + returns (address[] memory) + { + return Sorting.sort(_validators, _weights); + } + + function validatingDoubleSignProof( + bytes calldata, /*_header1*/ + bytes calldata /*_header2*/ + ) external pure returns (bool _validEvidence) { + return true; + } +} diff --git a/contracts/mocks/MockRoninValidatorSetExtended.sol b/contracts/mocks/MockRoninValidatorSetExtended.sol index bb7f014b2..53533d2ec 100644 --- a/contracts/mocks/MockRoninValidatorSetExtended.sol +++ b/contracts/mocks/MockRoninValidatorSetExtended.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.9; import "../validator/RoninValidatorSet.sol"; +import "../libraries/Sorting.sol"; contract MockRoninValidatorSetExtended is RoninValidatorSet { uint256[] internal _epochs; @@ -79,4 +80,13 @@ contract MockRoninValidatorSetExtended is RoninValidatorSet { return _candidates; } + + function _sortCandidates(address[] memory _candidates, uint256[] memory _weights) + internal + pure + override + returns (address[] memory _result) + { + return Sorting.sort(_candidates, _weights); + } } diff --git a/contracts/mocks/MockRoninValidatorSetSorting.sol b/contracts/mocks/MockRoninValidatorSetSorting.sol new file mode 100644 index 000000000..c63141f16 --- /dev/null +++ b/contracts/mocks/MockRoninValidatorSetSorting.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../validator/RoninValidatorSet.sol"; +import "../libraries/Sorting.sol"; + +contract MockRoninValidatorSetSorting is RoninValidatorSet { + constructor() {} + + function _sortCandidates(address[] memory _candidates, uint256[] memory _weights) + internal + pure + override + returns (address[] memory _result) + { + return Sorting.sort(_candidates, _weights); + } +} diff --git a/contracts/mocks/precompile-usages/MockPrecompileUsageSortValidators.sol b/contracts/mocks/precompile-usages/MockPrecompileUsageSortValidators.sol new file mode 100644 index 000000000..8a52127a9 --- /dev/null +++ b/contracts/mocks/precompile-usages/MockPrecompileUsageSortValidators.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../../precompile-usages/PrecompileUsageSortValidators.sol"; + +contract MockPrecompileUsageSortValidators is PrecompileUsageSortValidators { + address internal _precompileSortValidatorAddress; + + constructor(address _precompile) { + setPrecompileSortValidatorAddress(_precompile); + } + + function setPrecompileSortValidatorAddress(address _addr) public { + _precompileSortValidatorAddress = _addr; + } + + function precompileSortValidatorsAddress() public view override returns (address) { + return _precompileSortValidatorAddress; + } + + function callPrecompile(address[] calldata _validators, uint256[] calldata _weights) + public + view + returns (address[] memory _result) + { + return _sortCandidates(_validators, _weights); + } +} diff --git a/contracts/mocks/precompile-usages/MockPrecompileUsageValidateDoubleSign.sol b/contracts/mocks/precompile-usages/MockPrecompileUsageValidateDoubleSign.sol new file mode 100644 index 000000000..0d70e11d5 --- /dev/null +++ b/contracts/mocks/precompile-usages/MockPrecompileUsageValidateDoubleSign.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../../precompile-usages/PrecompileUsageValidateDoubleSign.sol"; + +contract MockPrecompileUsageValidateDoubleSign is PrecompileUsageValidateDoubleSign { + address internal _precompileValidateDoubleSignAddress; + + constructor(address _precompile) { + setPrecompileValidateDoubleSignAddress(_precompile); + } + + function setPrecompileValidateDoubleSignAddress(address _addr) public { + _precompileValidateDoubleSignAddress = _addr; + } + + function precompileValidateDoubleSignAddress() public view override returns (address) { + return _precompileValidateDoubleSignAddress; + } + + function callPrecompile(bytes calldata _header1, bytes calldata _header2) public view returns (bool) { + return _validateEvidence(_header1, _header2); + } +} diff --git a/contracts/precompile-usages/PrecompileUsageSortValidators.sol b/contracts/precompile-usages/PrecompileUsageSortValidators.sol new file mode 100644 index 000000000..91fa919fd --- /dev/null +++ b/contracts/precompile-usages/PrecompileUsageSortValidators.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +abstract contract PrecompileUsageSortValidators { + /// @dev Gets the address of the precompile of sorting validators + function precompileSortValidatorsAddress() public view virtual returns (address); + + /** + * @dev Sorting candidates descending by their weights by calling precompile contract. + * + * Note: This function is marked as virtual for being wrapping in mock contract for testing purpose. + */ + function _sortCandidates(address[] memory _candidates, uint256[] memory _weights) + internal + view + virtual + returns (address[] memory _result) + { + address _smc = precompileSortValidatorsAddress(); + bool _success = true; + + bytes memory _payload = abi.encodeWithSignature("sortValidators(address[],uint256[])", _candidates, _weights); + uint256 _payloadLength = _payload.length; + uint256 _resultLength = 0x20 * _candidates.length + 0x40; + + assembly { + let _payloadStart := add(_payload, 0x20) + + if iszero(staticcall(gas(), _smc, _payloadStart, _payloadLength, _result, _resultLength)) { + _success := 0 + } + + if iszero(returndatasize()) { + _success := 0 + } + + _result := add(_result, 0x20) + } + + require(_success, "PrecompileUsageSortValidators: call to precompile fails"); + } +} diff --git a/contracts/precompile-usages/PrecompileUsageValidateDoubleSign.sol b/contracts/precompile-usages/PrecompileUsageValidateDoubleSign.sol new file mode 100644 index 000000000..c1b81c6b3 --- /dev/null +++ b/contracts/precompile-usages/PrecompileUsageValidateDoubleSign.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +abstract contract PrecompileUsageValidateDoubleSign { + /// @dev Gets the address of the precompile of validating double sign evidence + function precompileValidateDoubleSignAddress() public view virtual returns (address); + + /** + * @dev Validate the two submitted block header if they are produced by the same address + * + * Note: The recover process is done by pre-compiled contract. This function is marked as + * virtual for implementing mocking contract for testing purpose. + */ + function _validateEvidence(bytes calldata _header1, bytes calldata _header2) + internal + view + virtual + returns (bool _validEvidence) + { + address _smc = precompileValidateDoubleSignAddress(); + bool _success = true; + + bytes memory _payload = abi.encodeWithSignature("validatingDoubleSignProof(bytes,bytes)", _header1, _header2); + uint _payloadLength = _payload.length; + uint[1] memory _output; + + assembly { + let _payloadStart := add(_payload, 0x20) + if iszero(staticcall(gas(), _smc, _payloadStart, _payloadLength, _output, 0x20)) { + _success := 0 + } + + if iszero(returndatasize()) { + _success := 0 + } + } + + require(_success, "PrecompileUsageValidateDoubleSign: call to precompile fails"); + return (_output[0] != 0); + } +} diff --git a/contracts/validator/RoninValidatorSet.sol b/contracts/validator/RoninValidatorSet.sol index ac6bfcc5a..346a3b391 100644 --- a/contracts/validator/RoninValidatorSet.sol +++ b/contracts/validator/RoninValidatorSet.sol @@ -11,12 +11,13 @@ import "../extensions/HasSlashIndicatorContract.sol"; import "../extensions/HasMaintenanceContract.sol"; import "../extensions/HasRoninTrustedOrganizationContract.sol"; import "../interfaces/IRoninValidatorSet.sol"; -import "../libraries/Sorting.sol"; import "../libraries/Math.sol"; +import "../precompile-usages/PrecompileUsageSortValidators.sol"; import "./CandidateManager.sol"; contract RoninValidatorSet is IRoninValidatorSet, + PrecompileUsageSortValidators, RONTransferHelper, HasStakingContract, HasStakingVestingContract, @@ -26,6 +27,8 @@ contract RoninValidatorSet is CandidateManager, Initializable { + /// @dev The address of the precompile of sorting validators + address internal constant _precompileSortValidatorAddress = address(0x66); /// @dev The maximum number of validator. uint256 internal _maxValidatorNumber; /// @dev The number of blocks in a epoch @@ -337,6 +340,13 @@ contract RoninValidatorSet is return _maxPrioritizedValidatorNumber; } + /** + * @inheritdoc PrecompileUsageSortValidators + */ + function precompileSortValidatorsAddress() public pure override returns (address) { + return _precompileSortValidatorAddress; + } + /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR ADMIN // /////////////////////////////////////////////////////////////////////////////////////// @@ -390,7 +400,7 @@ contract RoninValidatorSet is mstore(_weights, _length) } - _candidateList = Sorting.sort(_candidateList, _weights); + _candidateList = _sortCandidates(_candidateList, _weights); } /** diff --git a/test/maintainance/Maintenance.test.ts b/test/maintainance/Maintenance.test.ts index 0bf4097cb..7ea12448a 100644 --- a/test/maintainance/Maintenance.test.ts +++ b/test/maintainance/Maintenance.test.ts @@ -12,19 +12,21 @@ import { SlashIndicator__factory, Staking, Staking__factory, + MockRoninValidatorSetSorting__factory, } from '../../src/types'; -import { initTest } from '../helpers/fixture'; +import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; import { EpochController } from '../helpers/ronin-validator-set'; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; -let governanceAdmin: SignerWithAddress; +let governor: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; let maintenanceContract: Maintenance; let slashContract: SlashIndicator; let stakingContract: Staking; let validatorContract: RoninValidatorSet; +let governanceAdmin: GovernanceAdminInterface; let localEpochController: EpochController; @@ -44,17 +46,22 @@ let currentBlock: number; describe('Maintenance test', () => { before(async () => { - [deployer, coinbase, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + [deployer, coinbase, governor, ...validatorCandidates] = await ethers.getSigners(); + governanceAdmin = new GovernanceAdminInterface(governor); const { maintenanceContractAddress, slashContractAddress, stakingContractAddress, validatorContractAddress } = await initTest('Maintenance')({ - governanceAdmin: governanceAdmin.address, + governanceAdmin: governor.address, misdemeanorThreshold, felonyThreshold, }); maintenanceContract = Maintenance__factory.connect(maintenanceContractAddress, deployer); slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); - validatorContract = RoninValidatorSet__factory.connect(validatorContractAddress, deployer); + validatorContract = MockRoninValidatorSetSorting__factory.connect(validatorContractAddress, deployer); + + const mockValidatorLogic = await new MockRoninValidatorSetSorting__factory(deployer).deploy(); + await mockValidatorLogic.deployed(); + await governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); validatorCandidates = validatorCandidates.slice(0, maxValidatorNumber); for (let i = 0; i < maxValidatorNumber; i++) { diff --git a/test/precompile-usages/PrecompileUsageSortValidators.test.ts b/test/precompile-usages/PrecompileUsageSortValidators.test.ts new file mode 100644 index 000000000..cd61960d4 --- /dev/null +++ b/test/precompile-usages/PrecompileUsageSortValidators.test.ts @@ -0,0 +1,57 @@ +import { expect } from 'chai'; +import { ethers } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; + +import { + MockPrecompile, + MockPrecompile__factory, + MockPrecompileUsageSortValidators, + MockPrecompileUsageSortValidators__factory, +} from '../../src/types'; +import { randomInt } from 'crypto'; + +let deployer: SignerWithAddress; +let validatorCandidates: SignerWithAddress[]; +let precompileSorting: MockPrecompile; +let usageSorting: MockPrecompileUsageSortValidators; + +describe('[Precompile] Sorting validators test', () => { + before(async () => { + [deployer, ...validatorCandidates] = await ethers.getSigners(); + + precompileSorting = await new MockPrecompile__factory(deployer).deploy(); + usageSorting = await new MockPrecompileUsageSortValidators__factory(deployer).deploy(precompileSorting.address); + }); + + it('Should the usage contract correctly configs the precompile address', async () => { + expect(await usageSorting.precompileSortValidatorsAddress()).eq(precompileSorting.address); + }); + + it('Should the usage contract can call the precompile address', async () => { + let numOfValidator = 21; + let validatorsAndWeights = Array.from({ length: numOfValidator }, (_, i) => { + return { + address: validatorCandidates[i].address, + balance: randomInt(numOfValidator * 10000), + }; + }); + + let sortedValidators = await usageSorting.callPrecompile( + validatorsAndWeights.map((_) => _.address), + validatorsAndWeights.map((_) => _.balance) + ); + + let expectingValidators = validatorsAndWeights + .sort((a, b) => (a.balance > b.balance ? -1 : 1)) + .map((_) => _.address); + + expect(sortedValidators).eql(expectingValidators); + }); + + it('Should the usage contract revert with proper message on calling the precompile contract fails', async () => { + await usageSorting.setPrecompileSortValidatorAddress(ethers.constants.AddressZero); + await expect(usageSorting.callPrecompile([validatorCandidates[0].address], [1])).revertedWith( + 'PrecompileUsageSortValidators: call to precompile fails' + ); + }); +}); diff --git a/test/precompile-usages/PrecompileUsageValidateDoubleSign.test.ts b/test/precompile-usages/PrecompileUsageValidateDoubleSign.test.ts new file mode 100644 index 000000000..329db4a03 --- /dev/null +++ b/test/precompile-usages/PrecompileUsageValidateDoubleSign.test.ts @@ -0,0 +1,45 @@ +import { expect } from 'chai'; +import { ethers } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; + +import { + MockPrecompile, + MockPrecompile__factory, + MockPrecompileUsageValidateDoubleSign, + MockPrecompileUsageValidateDoubleSign__factory, +} from '../../src/types'; + +let deployer: SignerWithAddress; +let validatorCandidates: SignerWithAddress[]; +let precompileValidating: MockPrecompile; +let usageValidating: MockPrecompileUsageValidateDoubleSign; + +describe('[Precompile] Validate double sign test', () => { + before(async () => { + [deployer, ...validatorCandidates] = await ethers.getSigners(); + + precompileValidating = await new MockPrecompile__factory(deployer).deploy(); + usageValidating = await new MockPrecompileUsageValidateDoubleSign__factory(deployer).deploy( + precompileValidating.address + ); + }); + + let header1 = ethers.utils.toUtf8Bytes('sampleHeader1'); + let header2 = ethers.utils.toUtf8Bytes('sampleHeader2'); + + it('Should the usage contract correctly configs the precompile address', async () => { + expect(await usageValidating.precompileValidateDoubleSignAddress()).eq(precompileValidating.address); + }); + + it('Should the usage contract can call the precompile address', async () => { + let sortedValidators = await usageValidating.callPrecompile(header1, header2); + expect(sortedValidators).eql(true); + }); + + it('Should the usage contract revert with proper message on calling the precompile contract fails', async () => { + await usageValidating.setPrecompileValidateDoubleSignAddress(ethers.constants.AddressZero); + await expect(usageValidating.callPrecompile(header1, header2)).revertedWith( + 'PrecompileUsageValidateDoubleSign: call to precompile fails' + ); + }); +}); diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index 57a44e2ae..b700d6f16 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -4,10 +4,10 @@ import { ethers, network } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { + MockRoninValidatorSetSorting__factory, MockSlashIndicatorExtended, MockSlashIndicatorExtended__factory, RoninValidatorSet, - RoninValidatorSet__factory, Staking, Staking__factory, } from '../../src/types'; @@ -84,9 +84,13 @@ describe('Slash indicator test', () => { ); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); - validatorContract = RoninValidatorSet__factory.connect(validatorContractAddress, deployer); + validatorContract = MockRoninValidatorSetSorting__factory.connect(validatorContractAddress, deployer); slashContract = MockSlashIndicatorExtended__factory.connect(slashContractAddress, deployer); + const mockValidatorLogic = await new MockRoninValidatorSetSorting__factory(deployer).deploy(); + await mockValidatorLogic.deployed(); + await governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); + mockSlashLogic = await new MockSlashIndicatorExtended__factory(deployer).deploy(); await mockSlashLogic.deployed(); await governanceAdmin.upgrade(slashContractAddress, mockSlashLogic.address); From 75c736de63cc10d1f3ee05280999ed35506ac195 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 18 Oct 2022 11:33:25 +0700 Subject: [PATCH 172/190] Add Governance Admin contracts (#29) * Move files * Move has contracts * Update RoninTrustedOrganization contract * Add Has contracts * Fix Ronin Validator Set contract * Add Governance Admin contract * Update deployment script & its tests * Add test * Fix rebase issue * Fix comments --- contracts/extensions/GovernanceAdmin.sol | 120 ++++++++ .../collections/HasBridgeContract.sol | 43 +++ .../HasMaintenanceContract.sol | 4 +- .../{ => collections}/HasProxyAdmin.sol | 0 .../HasRoninTrustedOrganizationContract.sol | 6 +- .../HasSlashIndicatorContract.sol | 4 +- .../{ => collections}/HasStakingContract.sol | 4 +- .../HasStakingVestingContract.sol | 4 +- .../HasValidatorContract.sol | 4 +- .../IsolatedGovernance.sol | 57 ++++ .../BOsGovernanceProposal.sol | 71 +++++ .../BOsGovernanceRelay.sol | 65 +++++ .../sequential-governance/CoreGovernance.sol | 263 ++++++++++++++++++ .../GovernanceProposal.sol | 161 +++++++++++ .../sequential-governance/GovernanceRelay.sol | 143 ++++++++++ contracts/interfaces/IBridge.sol | 22 ++ contracts/interfaces/IQuorum.sol | 41 +++ .../interfaces/IRoninTrustedOrganization.sol | 49 +++- .../collections/IHasBridgeContract.sol | 25 ++ .../consumers/SignatureConsumer.sol | 10 + .../consumers/VoteStatusConsumer.sol | 11 + .../consumers/WeightedAddressConsumer.sol | 9 + contracts/libraries/Ballot.sol | 20 ++ contracts/libraries/BridgeOperatorsBallot.sol | 42 +++ contracts/libraries/GlobalProposal.sol | 78 ++++++ contracts/libraries/Proposal.sol | 101 +++++++ .../mainchain/MainchainGovernanceAdmin.sol | 99 +++++++ contracts/mocks/MockBridge.sol | 22 ++ .../mocks/MockRoninValidatorSetExtended.sol | 2 +- .../mocks/MockRoninValidatorSetSorting.sol | 2 +- .../mocks/MockSlashIndicatorExtended.sol | 2 +- contracts/mocks/MockStaking.sol | 2 +- contracts/mocks/MockValidatorSet.sol | 2 +- .../multi-chains/RoninTrustedOrganization.sol | 149 ++++++++-- contracts/{ => ronin}/Maintenance.sol | 10 +- contracts/ronin/RoninGovernanceAdmin.sol | 210 ++++++++++++++ contracts/{ => ronin}/SlashIndicator.sol | 10 +- contracts/{ => ronin}/StakingVesting.sol | 6 +- .../{ => ronin}/staking/RewardCalculation.sol | 2 +- contracts/{ => ronin}/staking/Staking.sol | 6 +- .../{ => ronin}/staking/StakingManager.sol | 8 +- .../validator/CandidateManager.sol | 8 +- .../validator/RoninValidatorSet.sol | 23 +- src/config.ts | 58 +++- src/deploy/calculate-address.ts | 24 +- src/deploy/logic/slash-indicator.ts | 1 + src/deploy/logic/staking-vesting.ts | 1 + src/deploy/mainchain-governance-admin.ts | 32 +++ ...nchain-ronin-trusted-organization-proxy.ts | 36 +++ src/deploy/proxy/maintenance-proxy.ts | 12 +- .../proxy/ronin-trusted-organization-proxy.ts | 14 +- src/deploy/proxy/ronin-validator-proxy.ts | 18 +- src/deploy/proxy/slash-indicator-proxy.ts | 12 +- src/deploy/proxy/staking-proxy.ts | 10 +- src/deploy/proxy/staking-vesting-proxy.ts | 10 +- src/deploy/ronin-governance-admin.ts | 30 ++ src/script/governance-admin-interface.ts | 81 ++++++ src/script/proposal.ts | 175 ++++++++++++ test/governance-admin/GovernanceAdmin.test.ts | 145 ++++++++++ test/helpers/fixture.ts | 59 ++-- .../integration/ActionSlashValidators.test.ts | 37 +-- test/integration/ActionSubmitReward.test.ts | 34 ++- test/integration/ActionWrapUpEpoch.test.ts | 34 ++- test/integration/Configuration.test.ts | 6 +- test/maintainance/Maintenance.test.ts | 31 ++- test/slash/SlashIndicator.test.ts | 24 +- test/validator/ArrangeValidators.test.ts | 35 ++- test/validator/RoninValidatorSet.test.ts | 34 ++- 68 files changed, 2617 insertions(+), 256 deletions(-) create mode 100644 contracts/extensions/GovernanceAdmin.sol create mode 100644 contracts/extensions/collections/HasBridgeContract.sol rename contracts/extensions/{ => collections}/HasMaintenanceContract.sol (90%) rename contracts/extensions/{ => collections}/HasProxyAdmin.sol (100%) rename contracts/extensions/{ => collections}/HasRoninTrustedOrganizationContract.sol (88%) rename contracts/extensions/{ => collections}/HasSlashIndicatorContract.sol (90%) rename contracts/extensions/{ => collections}/HasStakingContract.sol (90%) rename contracts/extensions/{ => collections}/HasStakingVestingContract.sol (90%) rename contracts/extensions/{ => collections}/HasValidatorContract.sol (90%) create mode 100644 contracts/extensions/isolated-governance/IsolatedGovernance.sol create mode 100644 contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceProposal.sol create mode 100644 contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceRelay.sol create mode 100644 contracts/extensions/sequential-governance/CoreGovernance.sol create mode 100644 contracts/extensions/sequential-governance/GovernanceProposal.sol create mode 100644 contracts/extensions/sequential-governance/GovernanceRelay.sol create mode 100644 contracts/interfaces/IBridge.sol create mode 100644 contracts/interfaces/IQuorum.sol create mode 100644 contracts/interfaces/collections/IHasBridgeContract.sol create mode 100644 contracts/interfaces/consumers/SignatureConsumer.sol create mode 100644 contracts/interfaces/consumers/VoteStatusConsumer.sol create mode 100644 contracts/interfaces/consumers/WeightedAddressConsumer.sol create mode 100644 contracts/libraries/Ballot.sol create mode 100644 contracts/libraries/BridgeOperatorsBallot.sol create mode 100644 contracts/libraries/GlobalProposal.sol create mode 100644 contracts/libraries/Proposal.sol create mode 100644 contracts/mainchain/MainchainGovernanceAdmin.sol create mode 100644 contracts/mocks/MockBridge.sol rename contracts/{ => ronin}/Maintenance.sol (96%) create mode 100644 contracts/ronin/RoninGovernanceAdmin.sol rename contracts/{ => ronin}/SlashIndicator.sol (97%) rename contracts/{ => ronin}/StakingVesting.sol (92%) rename contracts/{ => ronin}/staking/RewardCalculation.sol (99%) rename contracts/{ => ronin}/staking/Staking.sol (97%) rename contracts/{ => ronin}/staking/StakingManager.sol (98%) rename contracts/{ => ronin}/validator/CandidateManager.sol (96%) rename contracts/{ => ronin}/validator/RoninValidatorSet.sol (95%) create mode 100644 src/deploy/mainchain-governance-admin.ts create mode 100644 src/deploy/proxy/mainchain-ronin-trusted-organization-proxy.ts create mode 100644 src/deploy/ronin-governance-admin.ts create mode 100644 src/script/governance-admin-interface.ts create mode 100644 src/script/proposal.ts create mode 100644 test/governance-admin/GovernanceAdmin.test.ts diff --git a/contracts/extensions/GovernanceAdmin.sol b/contracts/extensions/GovernanceAdmin.sol new file mode 100644 index 000000000..d1f68d31d --- /dev/null +++ b/contracts/extensions/GovernanceAdmin.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../extensions/sequential-governance/CoreGovernance.sol"; +import "../extensions/collections/HasRoninTrustedOrganizationContract.sol"; +import "../extensions/collections/HasBridgeContract.sol"; +import "../interfaces/IRoninTrustedOrganization.sol"; + +contract GovernanceAdmin is CoreGovernance, HasRoninTrustedOrganizationContract, HasBridgeContract { + /// @dev Domain separator + bytes32 public constant DOMAIN_SEPARATOR = 0xf8704f8860d9e985bf6c52ec4738bd10fe31487599b36c0944f746ea09dc256b; + + modifier onlySelfCall() { + require(msg.sender == address(this), "GovernanceAdmin: only allowed self-call"); + _; + } + + constructor(address _roninTrustedOrganizationContract, address _bridgeContract) { + require( + keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,bytes32 salt)"), + keccak256("GovernanceAdmin"), // name hash + keccak256("1"), // version hash + keccak256(abi.encode("RONIN_GOVERNANCE_ADMIN", 2020)) // salt + ) + ) == DOMAIN_SEPARATOR, + "GovernanceAdmin: invalid domain" + ); + _setRoninTrustedOrganizationContract(_roninTrustedOrganizationContract); + _setBridgeContract(_bridgeContract); + } + + /** + * @inheritdoc IHasRoninTrustedOrganizationContract + */ + function setRoninTrustedOrganizationContract(address _addr) external override onlySelfCall { + _setRoninTrustedOrganizationContract(_addr); + } + + /** + * @inheritdoc IHasBridgeContract + */ + function setBridgeContract(address _addr) external override onlySelfCall { + _setBridgeContract(_addr); + } + + /** + * @dev Returns the current implementation of `_proxy`. + * + * Requirements: + * - This contract must be the admin of `_proxy`. + * + */ + function getProxyImplementation(address _proxy) external view returns (address) { + // We need to manually run the static call since the getter cannot be flagged as view + // bytes4(keccak256("implementation()")) == 0x5c60da1b + (bool _success, bytes memory _returndata) = _proxy.staticcall(hex"5c60da1b"); + require(_success, "GovernanceAdmin: proxy call `implementation()` failed"); + return abi.decode(_returndata, (address)); + } + + /** + * @dev Returns the current admin of `_proxy`. + * + * Requirements: + * - This contract must be the admin of `_proxy`. + * + */ + function getProxyAdmin(address _proxy) external view returns (address) { + // We need to manually run the static call since the getter cannot be flagged as view + // bytes4(keccak256("admin()")) == 0xf851a440 + (bool _success, bytes memory _returndata) = _proxy.staticcall(hex"f851a440"); + require(_success, "GovernanceAdmin: proxy call `admin()` failed"); + return abi.decode(_returndata, (address)); + } + + /** + * @dev Changes the admin of `_proxy` to `newAdmin`. + * + * Requirements: + * - This contract must be the current admin of `_proxy`. + * + */ + function changeProxyAdmin(address _proxy, address _newAdmin) external onlySelfCall { + // bytes4(keccak256("changeAdmin(address)")) + (bool _success, ) = _proxy.call(abi.encodeWithSelector(0x8f283970, _newAdmin)); + require(_success, "GovernanceAdmin: proxy call `changeAdmin(address)` failed"); + } + + /** + * @dev Override {CoreGovernance-_getMinimumVoteWeight}. + */ + function _getMinimumVoteWeight() internal view virtual override returns (uint256) { + (bool _success, bytes memory _returndata) = roninTrustedOrganizationContract().staticcall( + abi.encodeWithSelector( + // TransparentUpgradeableProxyV2.functionDelegateCall.selector, + 0x4bb5274a, + abi.encodeWithSelector(IQuorum.minimumVoteWeight.selector) + ) + ); + require(_success, "GovernanceAdmin: proxy call `minimumVoteWeight()` failed"); + return abi.decode(_returndata, (uint256)); + } + + /** + * @dev Override {CoreGovernance-_getTotalWeights}. + */ + function _getTotalWeights() internal view virtual override returns (uint256) { + (bool _success, bytes memory _returndata) = roninTrustedOrganizationContract().staticcall( + abi.encodeWithSelector( + // TransparentUpgradeableProxyV2.functionDelegateCall.selector, + 0x4bb5274a, + abi.encodeWithSelector(IRoninTrustedOrganization.totalWeights.selector) + ) + ); + require(_success, "GovernanceAdmin: proxy call `totalWeights()` failed"); + return abi.decode(_returndata, (uint256)); + } +} diff --git a/contracts/extensions/collections/HasBridgeContract.sol b/contracts/extensions/collections/HasBridgeContract.sol new file mode 100644 index 000000000..4360498c0 --- /dev/null +++ b/contracts/extensions/collections/HasBridgeContract.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./HasProxyAdmin.sol"; +import "../../interfaces/collections/IHasBridgeContract.sol"; +import "../../interfaces/IBridge.sol"; + +contract HasBridgeContract is IHasBridgeContract, HasProxyAdmin { + IBridge internal _bridgeContract; + + modifier onlyBridgeContract() { + require(bridgeContract() == msg.sender, "HasBridgeContract: method caller must be bridge contract"); + _; + } + + /** + * @inheritdoc IHasBridgeContract + */ + function bridgeContract() public view override returns (address) { + return address(_bridgeContract); + } + + /** + * @inheritdoc IHasBridgeContract + */ + function setBridgeContract(address _addr) external virtual override onlyAdmin { + _setBridgeContract(_addr); + } + + /** + * @dev Sets the bridge contract. + * + * Requirements: + * - The new address is a contract. + * + * Emits the event `BridgeContractUpdated`. + * + */ + function _setBridgeContract(address _addr) internal { + _bridgeContract = IBridge(_addr); + emit BridgeContractUpdated(_addr); + } +} diff --git a/contracts/extensions/HasMaintenanceContract.sol b/contracts/extensions/collections/HasMaintenanceContract.sol similarity index 90% rename from contracts/extensions/HasMaintenanceContract.sol rename to contracts/extensions/collections/HasMaintenanceContract.sol index 389afc79d..1dbd49b1c 100644 --- a/contracts/extensions/HasMaintenanceContract.sol +++ b/contracts/extensions/collections/HasMaintenanceContract.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import "./HasProxyAdmin.sol"; -import "../interfaces/collections/IHasMaintenanceContract.sol"; -import "../interfaces/IMaintenance.sol"; +import "../../interfaces/collections/IHasMaintenanceContract.sol"; +import "../../interfaces/IMaintenance.sol"; contract HasMaintenanceContract is IHasMaintenanceContract, HasProxyAdmin { IMaintenance internal _maintenanceContract; diff --git a/contracts/extensions/HasProxyAdmin.sol b/contracts/extensions/collections/HasProxyAdmin.sol similarity index 100% rename from contracts/extensions/HasProxyAdmin.sol rename to contracts/extensions/collections/HasProxyAdmin.sol diff --git a/contracts/extensions/HasRoninTrustedOrganizationContract.sol b/contracts/extensions/collections/HasRoninTrustedOrganizationContract.sol similarity index 88% rename from contracts/extensions/HasRoninTrustedOrganizationContract.sol rename to contracts/extensions/collections/HasRoninTrustedOrganizationContract.sol index 13b866ccd..c648612bd 100644 --- a/contracts/extensions/HasRoninTrustedOrganizationContract.sol +++ b/contracts/extensions/collections/HasRoninTrustedOrganizationContract.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import "./HasProxyAdmin.sol"; -import "../interfaces/collections/IHasRoninTrustedOrganizationContract.sol"; -import "../interfaces/IRoninTrustedOrganization.sol"; +import "../../interfaces/collections/IHasRoninTrustedOrganizationContract.sol"; +import "../../interfaces/IRoninTrustedOrganization.sol"; contract HasRoninTrustedOrganizationContract is IHasRoninTrustedOrganizationContract, HasProxyAdmin { IRoninTrustedOrganization internal _roninTrustedOrganizationContract; @@ -26,7 +26,7 @@ contract HasRoninTrustedOrganizationContract is IHasRoninTrustedOrganizationCont /** * @inheritdoc IHasRoninTrustedOrganizationContract */ - function setRoninTrustedOrganizationContract(address _addr) external override onlyAdmin { + function setRoninTrustedOrganizationContract(address _addr) external virtual override onlyAdmin { _setRoninTrustedOrganizationContract(_addr); } diff --git a/contracts/extensions/HasSlashIndicatorContract.sol b/contracts/extensions/collections/HasSlashIndicatorContract.sol similarity index 90% rename from contracts/extensions/HasSlashIndicatorContract.sol rename to contracts/extensions/collections/HasSlashIndicatorContract.sol index 6700135b7..8b352ea9d 100644 --- a/contracts/extensions/HasSlashIndicatorContract.sol +++ b/contracts/extensions/collections/HasSlashIndicatorContract.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import "./HasProxyAdmin.sol"; -import "../interfaces/collections/IHasSlashIndicatorContract.sol"; -import "../interfaces/ISlashIndicator.sol"; +import "../../interfaces/collections/IHasSlashIndicatorContract.sol"; +import "../../interfaces/ISlashIndicator.sol"; contract HasSlashIndicatorContract is IHasSlashIndicatorContract, HasProxyAdmin { ISlashIndicator internal _slashIndicatorContract; diff --git a/contracts/extensions/HasStakingContract.sol b/contracts/extensions/collections/HasStakingContract.sol similarity index 90% rename from contracts/extensions/HasStakingContract.sol rename to contracts/extensions/collections/HasStakingContract.sol index 65b582599..bf5155c40 100644 --- a/contracts/extensions/HasStakingContract.sol +++ b/contracts/extensions/collections/HasStakingContract.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import "./HasProxyAdmin.sol"; -import "../interfaces/collections/IHasStakingContract.sol"; -import "../interfaces/IStaking.sol"; +import "../../interfaces/collections/IHasStakingContract.sol"; +import "../../interfaces/IStaking.sol"; contract HasStakingContract is IHasStakingContract, HasProxyAdmin { IStaking internal _stakingContract; diff --git a/contracts/extensions/HasStakingVestingContract.sol b/contracts/extensions/collections/HasStakingVestingContract.sol similarity index 90% rename from contracts/extensions/HasStakingVestingContract.sol rename to contracts/extensions/collections/HasStakingVestingContract.sol index 331be5d1f..4ea3c58c8 100644 --- a/contracts/extensions/HasStakingVestingContract.sol +++ b/contracts/extensions/collections/HasStakingVestingContract.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import "./HasProxyAdmin.sol"; -import "../interfaces/collections/IHasStakingVestingContract.sol"; -import "../interfaces/IStakingVesting.sol"; +import "../../interfaces/collections/IHasStakingVestingContract.sol"; +import "../../interfaces/IStakingVesting.sol"; contract HasStakingVestingContract is IHasStakingVestingContract, HasProxyAdmin { IStakingVesting internal _stakingVestingContract; diff --git a/contracts/extensions/HasValidatorContract.sol b/contracts/extensions/collections/HasValidatorContract.sol similarity index 90% rename from contracts/extensions/HasValidatorContract.sol rename to contracts/extensions/collections/HasValidatorContract.sol index 9fd95506b..329503540 100644 --- a/contracts/extensions/HasValidatorContract.sol +++ b/contracts/extensions/collections/HasValidatorContract.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import "./HasProxyAdmin.sol"; -import "../interfaces/collections/IHasValidatorContract.sol"; -import "../interfaces/IRoninValidatorSet.sol"; +import "../../interfaces/collections/IHasValidatorContract.sol"; +import "../../interfaces/IRoninValidatorSet.sol"; contract HasValidatorContract is IHasValidatorContract, HasProxyAdmin { IRoninValidatorSet internal _validatorContract; diff --git a/contracts/extensions/isolated-governance/IsolatedGovernance.sol b/contracts/extensions/isolated-governance/IsolatedGovernance.sol new file mode 100644 index 000000000..6951b8f81 --- /dev/null +++ b/contracts/extensions/isolated-governance/IsolatedGovernance.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/Strings.sol"; +import "../../interfaces/consumers/VoteStatusConsumer.sol"; + +abstract contract IsolatedGovernance is VoteStatusConsumer { + struct IsolatedVote { + VoteStatus status; + bytes32 finalHash; + /// @dev Mapping from voter => receipt hash + mapping(address => bytes32) voteHashOf; + /// @dev Mapping from receipt hash => vote weight + mapping(bytes32 => uint256) weight; + } + + /** + * @dev Casts vote for the receipt with the receipt hash `_hash`. + * + * Requirements: + * - The vote is not finalized. + * - The voter has not voted for the round. + * + */ + function _castVote( + IsolatedVote storage _proposal, + address _voter, + uint256 _voterWeight, + uint256 _minimumVoteWeight, + bytes32 _hash + ) internal virtual returns (VoteStatus _status) { + if (_voted(_proposal, _voter)) { + revert( + string(abi.encodePacked("IsolatedGovernance: ", Strings.toHexString(uint160(_voter), 20), " already voted")) + ); + } + + // Record for voter + _proposal.voteHashOf[_voter] = _hash; + // Increase vote weight + uint256 _weight = _proposal.weight[_hash] += _voterWeight; + + if (_weight >= _minimumVoteWeight && _proposal.status == VoteStatus.Pending) { + _proposal.status = VoteStatus.Approved; + _proposal.finalHash = _hash; + } + + _status = _proposal.status; + } + + /** + * @dev Returns whether the voter casted for the proposal. + */ + function _voted(IsolatedVote storage _proposal, address _voter) internal view virtual returns (bool) { + return _proposal.voteHashOf[_voter] != bytes32(0); + } +} diff --git a/contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceProposal.sol b/contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceProposal.sol new file mode 100644 index 000000000..e303dfb56 --- /dev/null +++ b/contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceProposal.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../../../extensions/isolated-governance/IsolatedGovernance.sol"; +import "../../../interfaces/consumers/WeightedAddressConsumer.sol"; +import "../../../interfaces/consumers/SignatureConsumer.sol"; +import "../../../libraries/BridgeOperatorsBallot.sol"; + +abstract contract BOsGovernanceProposal is SignatureConsumer, WeightedAddressConsumer, IsolatedGovernance { + /// @dev The last period that the brige operators synced. + uint256 internal _lastSyncedPeriod; + /// @dev Mapping from period index => bridge operators vote + mapping(uint256 => IsolatedVote) internal _vote; + + /// @dev Mapping from governor address => last block that the governor voted + mapping(address => uint256) internal _lastVotedBlock; + /// @dev Mapping from period => voter => signatures + mapping(uint256 => mapping(address => Signature)) internal _votingSig; + + /** + * @dev Votes for a set of bridge operators by signatures. + * + * Requirements: + * - The period of voting is larger than the last synced period. + * - The arrays are not empty. + * - The signature signers are in order. + * + */ + function _castVotesBySignatures( + WeightedAddress[] calldata _operators, + Signature[] calldata _signatures, + uint256 _period, + uint256 _minimumVoteWeight, + bytes32 _domainSeperator + ) internal { + require(_period >= _lastSyncedPeriod, "BOsGovernanceProposal: query for outdated period"); + require(_operators.length > 0 && _signatures.length > 0, "BOsGovernanceProposal: invalid array length"); + + Signature memory _sig; + address _signer; + address _lastSigner; + bytes32 _hash = BridgeOperatorsBallot.hash(_period, _operators); + bytes32 _digest = ECDSA.toTypedDataHash(_domainSeperator, _hash); + IsolatedVote storage _v = _vote[_period]; + bool _hasValidVotes; + + for (uint256 _i = 0; _i < _signatures.length; _i++) { + _sig = _signatures[_i]; + _signer = ECDSA.recover(_digest, _sig.v, _sig.r, _sig.s); + require(_lastSigner < _signer, "BOsGovernanceProposal: invalid order"); + _lastSigner = _signer; + + uint256 _weight = _getWeight(_signer); + if (_weight > 0) { + _hasValidVotes = true; + _lastVotedBlock[_signer] = block.number; + _votingSig[_period][_signer] = _sig; + if (_castVote(_v, _signer, _weight, _minimumVoteWeight, _hash) == VoteStatus.Approved) { + return; + } + } + } + + require(_hasValidVotes, "BOsGovernanceProposal: invalid signatures"); + } + + /** + * @dev Returns the weight of a governor. + */ + function _getWeight(address _governor) internal view virtual returns (uint256); +} diff --git a/contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceRelay.sol b/contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceRelay.sol new file mode 100644 index 000000000..3bc816e1b --- /dev/null +++ b/contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceRelay.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../../../extensions/isolated-governance/IsolatedGovernance.sol"; +import "../../../interfaces/consumers/WeightedAddressConsumer.sol"; +import "../../../interfaces/consumers/SignatureConsumer.sol"; +import "../../../libraries/BridgeOperatorsBallot.sol"; + +abstract contract BOsGovernanceRelay is SignatureConsumer, WeightedAddressConsumer, IsolatedGovernance { + /// @dev The last period that the brige operators synced. + uint256 internal _lastSyncedPeriod; + /// @dev Mapping from period index => bridge operators vote + mapping(uint256 => IsolatedVote) internal _vote; + + /** + * @dev Relays votes by signatures. + * + * Requirements: + * - The period of voting is larger than the last synced period. + * - The arrays are not empty. + * - The signature signers are in order. + * + * @notice Does not store the voter signature into storage. + * + */ + function _relayVotesBySignatures( + WeightedAddress[] calldata _operators, + Signature[] calldata _signatures, + uint256 _period, + uint256 _minimumVoteWeight, + bytes32 _domainSeperator + ) internal { + require(_period > _lastSyncedPeriod, "BOsGovernanceRelay: query for outdated period"); + require(_operators.length > 0 && _signatures.length > 0, "BOsGovernanceRelay: invalid array length"); + + Signature memory _sig; + address[] memory _signers = new address[](_signatures.length); + address _lastSigner; + bytes32 _hash = BridgeOperatorsBallot.hash(_period, _operators); + bytes32 _digest = ECDSA.toTypedDataHash(_domainSeperator, _hash); + + for (uint256 _i = 0; _i < _signatures.length; _i++) { + _sig = _signatures[_i]; + _signers[_i] = ECDSA.recover(_digest, _sig.v, _sig.r, _sig.s); + require(_lastSigner < _signers[_i], "BOsGovernanceRelay: invalid order"); + _lastSigner = _signers[_i]; + } + + IsolatedVote storage _v = _vote[_period]; + uint256 _totalVoteWeight = _getWeights(_signers); + if (_totalVoteWeight >= _minimumVoteWeight) { + require(_totalVoteWeight > 0, "BOsGovernanceRelay: invalid vote weight"); + _v.status = VoteStatus.Approved; + _lastSyncedPeriod = _period; + return; + } + + revert("BOsGovernanceRelay: relay failed"); + } + + /** + * @dev Returns the weight of the governor list. + */ + function _getWeights(address[] memory _governors) internal view virtual returns (uint256); +} diff --git a/contracts/extensions/sequential-governance/CoreGovernance.sol b/contracts/extensions/sequential-governance/CoreGovernance.sol new file mode 100644 index 000000000..2389e82ec --- /dev/null +++ b/contracts/extensions/sequential-governance/CoreGovernance.sol @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/Strings.sol"; +import "../../libraries/Proposal.sol"; +import "../../libraries/GlobalProposal.sol"; +import "../../libraries/Ballot.sol"; +import "../../interfaces/consumers/SignatureConsumer.sol"; +import "../../interfaces/consumers/VoteStatusConsumer.sol"; + +abstract contract CoreGovernance is SignatureConsumer, VoteStatusConsumer { + using Proposal for Proposal.ProposalDetail; + using GlobalProposal for GlobalProposal.GlobalProposalDetail; + + struct ProposalVote { + VoteStatus status; + bytes32 hash; + uint256 againstVoteWeight; // Total weight of against votes + uint256 forVoteWeight; // Total weight of for votes + mapping(address => bool) forVoted; + mapping(address => bool) againstVoted; + mapping(address => Signature) sig; + } + + /// @dev Emitted when a proposal is created + event ProposalCreated( + uint256 indexed chainId, + uint256 indexed round, + bytes32 indexed proposalHash, + Proposal.ProposalDetail proposal, + address creator + ); + /// @dev Emitted when a proposal is created + event GlobalProposalCreated( + uint256 indexed round, + bytes32 indexed proposalHash, + Proposal.ProposalDetail proposal, + bytes32 globalProposalHash, + GlobalProposal.GlobalProposalDetail globalProposal, + address creator + ); + /// @dev Emitted when the proposal is voted + event ProposalVoted(bytes32 indexed proposalHash, address indexed voter, Ballot.VoteType support, uint256 weight); + /// @dev Emitted when the proposal is approved + event ProposalApproved(bytes32 indexed proposalHash); + /// @dev Emitted when the vote is reject + event ProposalRejected(bytes32 indexed proposalHash); + /// @dev Emitted when the proposal is executed + event ProposalExecuted(bytes32 indexed proposalHash, bool[] successCalls, bytes[] returnDatas); + + /// @dev Mapping from chain id => vote round + /// @notice chain id = 0 for global proposal + mapping(uint256 => uint256) public round; + /// @dev Mapping from chain id => vote round => proposal vote + mapping(uint256 => mapping(uint256 => ProposalVote)) public vote; + + /** + * @dev Creates new round voting for the proposal `_proposalHash` of chain `_chainId`. + */ + function _createVotingRound(uint256 _chainId, bytes32 _proposalHash) internal returns (uint256 _round) { + _round = round[_chainId]++; + // Skip checking for the first ever round + if (_round > 0) { + require(vote[_chainId][_round].status != VoteStatus.Pending, "CoreGovernance: current proposal is not completed"); + } + vote[_chainId][++_round].hash = _proposalHash; + } + + /** + * @dev Proposes for a new proposal. + * + * Requirements: + * - The chain id is not equal to 0. + * + * Emits the `ProposalCreated` event. + * + */ + function _proposeProposal( + uint256 _chainId, + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas, + uint256[] memory _gasAmounts, + address _creator + ) internal virtual returns (uint256 _round) { + require(_chainId != 0, "CoreGovernance: invalid chain id"); + + Proposal.ProposalDetail memory _proposal = Proposal.ProposalDetail( + round[_chainId] + 1, + _chainId, + _targets, + _values, + _calldatas, + _gasAmounts + ); + _proposal.validate(); + + bytes32 _proposalHash = _proposal.hash(); + _round = _createVotingRound(_chainId, _proposalHash); + emit ProposalCreated(_chainId, _round, _proposalHash, _proposal, _creator); + } + + /** + * @dev Proposes proposal struct. + * + * Requirements: + * - The chain id is not equal to 0. + * - The proposal nonce is equal to the new round. + * + * Emits the `ProposalCreated` event. + * + */ + function _proposeProposalStruct(Proposal.ProposalDetail memory _proposal, address _creator) + internal + virtual + returns (uint256 _round) + { + uint256 _chainId = _proposal.chainId; + require(_chainId != 0, "CoreGovernance: invalid chain id"); + _proposal.validate(); + + bytes32 _proposalHash = _proposal.hash(); + _round = _createVotingRound(_chainId, _proposalHash); + require(_round == _proposal.nonce, "CoreGovernance: invalid proposal nonce"); + emit ProposalCreated(_chainId, _round, _proposalHash, _proposal, _creator); + } + + /** + * @dev Proposes for a global proposal. + * + * Emits the `GlobalProposalCreated` event. + * + */ + function _proposeGlobal( + GlobalProposal.TargetOption[] calldata _targetOptions, + uint256[] memory _values, + bytes[] memory _calldatas, + uint256[] memory _gasAmounts, + address _roninTrustedOrganizationContract, + address _gatewayContract, + address _creator + ) internal virtual returns (uint256 _round) { + GlobalProposal.GlobalProposalDetail memory _globalProposal = GlobalProposal.GlobalProposalDetail( + round[0] + 1, + _targetOptions, + _values, + _calldatas, + _gasAmounts + ); + Proposal.ProposalDetail memory _proposal = _globalProposal.into_proposal_detail( + _roninTrustedOrganizationContract, + _gatewayContract + ); + _proposal.validate(); + + bytes32 _proposalHash = _proposal.hash(); + _round = _createVotingRound(0, _proposalHash); + emit GlobalProposalCreated(_round, _proposalHash, _proposal, _globalProposal.hash(), _globalProposal, _creator); + } + + /** + * @dev Proposes global proposal struct. + * + * Requirements: + * - The proposal nonce is equal to the new round. + * + * Emits the `GlobalProposalCreated` event. + * + */ + function _proposeGlobalStruct( + GlobalProposal.GlobalProposalDetail memory _globalProposal, + address _roninTrustedOrganizationContract, + address _gatewayContract, + address _creator + ) internal virtual returns (Proposal.ProposalDetail memory _proposal, uint256 _round) { + _proposal = _globalProposal.into_proposal_detail(_roninTrustedOrganizationContract, _gatewayContract); + _proposal.validate(); + + bytes32 _proposalHash = _proposal.hash(); + _round = _createVotingRound(0, _proposalHash); + require(_round == _proposal.nonce, "CoreGovernance: invalid proposal nonce"); + emit GlobalProposalCreated(_round, _proposalHash, _proposal, _globalProposal.hash(), _globalProposal, _creator); + } + + /** + * @dev Casts vote for the proposal with data and returns whether the voting is done. + * + * Requirements: + * - The proposal nonce is equal to the round. + * - The vote is not finalized. + * - The voter has not voted for the round. + * + * Emits the `ProposalVoted` event. Emits the `ProposalApproved`, `ProposalExecuted` or `ProposalRejected` once the + * proposal is approved, executed or rejected. + * + */ + function _castVote( + Proposal.ProposalDetail memory _proposal, + Ballot.VoteType _support, + uint256 _minimumForVoteWeight, + uint256 _minimumAgainstVoteWeight, + address _voter, + Signature memory _signature, + uint256 _voterWeight + ) internal virtual returns (bool _done) { + uint256 _chainId = _proposal.chainId; + uint256 _round = _proposal.nonce; + ProposalVote storage _vote = vote[_chainId][_round]; + + require(round[_proposal.chainId] == _round, "CoreGovernance: query for invalid proposal nonce"); + require(_vote.status == VoteStatus.Pending, "CoreGovernance: the vote is finalized"); + if (_vote.forVoted[_voter] || _vote.againstVoted[_voter]) { + revert(string(abi.encodePacked("CoreGovernance: ", Strings.toHexString(uint160(_voter), 20), " already voted"))); + } + + _vote.sig[_voter] = _signature; + emit ProposalVoted(_vote.hash, _voter, _support, _voterWeight); + + uint256 _forVoteWeight; + uint256 _againstVoteWeight; + if (_support == Ballot.VoteType.For) { + _vote.forVoted[_voter] = true; + _forVoteWeight = _vote.forVoteWeight += _voterWeight; + } else if (_support == Ballot.VoteType.Against) { + _vote.againstVoted[_voter] = true; + _againstVoteWeight = _vote.againstVoteWeight += _voterWeight; + } else { + revert("CoreGovernance: unsupported vote type"); + } + + if (_forVoteWeight >= _minimumForVoteWeight) { + _done = true; + _vote.status = VoteStatus.Approved; + emit ProposalApproved(_vote.hash); + _tryExecute(_vote, _proposal); + } else if (_againstVoteWeight >= _minimumAgainstVoteWeight) { + _done = true; + _vote.status = VoteStatus.Rejected; + emit ProposalRejected(_vote.hash); + } + } + + /** + * @dev Executes the proposal and update the vote status once the proposal is executable. + */ + function _tryExecute(ProposalVote storage _vote, Proposal.ProposalDetail memory _proposal) internal { + if (_proposal.executable()) { + _vote.status = VoteStatus.Executed; + (bool[] memory _successCalls, bytes[] memory _returnDatas) = _proposal.execute(); + emit ProposalExecuted(_vote.hash, _successCalls, _returnDatas); + } + } + + /** + * @dev Returns total weight from validators. + */ + function _getTotalWeights() internal view virtual returns (uint256); + + /** + * @dev Returns minimum vote to pass a proposal. + */ + function _getMinimumVoteWeight() internal view virtual returns (uint256); +} diff --git a/contracts/extensions/sequential-governance/GovernanceProposal.sol b/contracts/extensions/sequential-governance/GovernanceProposal.sol new file mode 100644 index 000000000..f61adc733 --- /dev/null +++ b/contracts/extensions/sequential-governance/GovernanceProposal.sol @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./CoreGovernance.sol"; + +abstract contract GovernanceProposal is CoreGovernance { + using Proposal for Proposal.ProposalDetail; + using GlobalProposal for GlobalProposal.GlobalProposalDetail; + + /** + * @dev Casts votes by signatures. + * + * @notice This method does not verify the proposal hash with the vote hash. Please consider checking it before. + * + */ + function _castVotesBySignatures( + Proposal.ProposalDetail memory _proposal, + Ballot.VoteType[] calldata _supports, + Signature[] calldata _signatures, + bytes32 _forDigest, + bytes32 _againstDigest + ) internal { + require(_supports.length > 0 && _supports.length == _signatures.length, "GovernanceProposal: invalid array length"); + uint256 _minimumForVoteWeight = _getMinimumVoteWeight(); + uint256 _minimumAgainstVoteWeight = _getTotalWeights() - _minimumForVoteWeight + 1; + + address _lastSigner; + address _signer; + Signature memory _sig; + bool _hasValidVotes; + for (uint256 _i; _i < _signatures.length; _i++) { + _sig = _signatures[_i]; + + if (_supports[_i] == Ballot.VoteType.For) { + _signer = ECDSA.recover(_forDigest, _sig.v, _sig.r, _sig.s); + } else if (_supports[_i] == Ballot.VoteType.Against) { + _signer = ECDSA.recover(_againstDigest, _sig.v, _sig.r, _sig.s); + } else { + revert("GovernanceProposal: query for unsupported vote type"); + } + + require(_lastSigner < _signer, "GovernanceProposal: invalid order"); + _lastSigner = _signer; + + uint256 _weight = _getWeight(_signer); + if (_weight > 0) { + _hasValidVotes = true; + if ( + _castVote(_proposal, _supports[_i], _minimumForVoteWeight, _minimumAgainstVoteWeight, _signer, _sig, _weight) + ) { + return; + } + } + } + + require(_hasValidVotes, "GovernanceProposal: invalid signatures"); + } + + /** + * @dev Proposes a proposal struct and casts votes by signature. + */ + function _proposeProposalStructAndCastVotes( + Proposal.ProposalDetail calldata _proposal, + Ballot.VoteType[] calldata _supports, + Signature[] calldata _signatures, + bytes32 _domainSeparator, + address _creator + ) internal { + _proposeProposalStruct(_proposal, _creator); + bytes32 _proposalHash = _proposal.hash(); + _castVotesBySignatures( + _proposal, + _supports, + _signatures, + ECDSA.toTypedDataHash(_domainSeparator, Ballot.hash(_proposalHash, Ballot.VoteType.For)), + ECDSA.toTypedDataHash(_domainSeparator, Ballot.hash(_proposalHash, Ballot.VoteType.Against)) + ); + } + + /** + * @dev Proposes a proposal struct and casts votes by signature. + */ + function _castProposalBySignatures( + Proposal.ProposalDetail calldata _proposal, + Ballot.VoteType[] calldata _supports, + Signature[] calldata _signatures, + bytes32 _domainSeparator + ) internal { + bytes32 _proposalHash = _proposal.hash(); + require( + vote[_proposal.chainId][_proposal.nonce].hash == _proposalHash, + "GovernanceAdmin: cast vote for invalid proposal" + ); + _castVotesBySignatures( + _proposal, + _supports, + _signatures, + ECDSA.toTypedDataHash(_domainSeparator, Ballot.hash(_proposalHash, Ballot.VoteType.For)), + ECDSA.toTypedDataHash(_domainSeparator, Ballot.hash(_proposalHash, Ballot.VoteType.Against)) + ); + } + + /** + * @dev Proposes and votes by signature. + */ + function _proposeGlobalProposalStructAndCastVotes( + GlobalProposal.GlobalProposalDetail calldata _globalProposal, + Ballot.VoteType[] calldata _supports, + Signature[] calldata _signatures, + bytes32 _domainSeparator, + address _roninTrustedOrganizationContract, + address _gatewayContract, + address _creator + ) internal returns (Proposal.ProposalDetail memory _proposal) { + (_proposal, ) = _proposeGlobalStruct( + _globalProposal, + _roninTrustedOrganizationContract, + _gatewayContract, + _creator + ); + bytes32 _globalProposalHash = _globalProposal.hash(); + _castVotesBySignatures( + _proposal, + _supports, + _signatures, + ECDSA.toTypedDataHash(_domainSeparator, Ballot.hash(_globalProposalHash, Ballot.VoteType.For)), + ECDSA.toTypedDataHash(_domainSeparator, Ballot.hash(_globalProposalHash, Ballot.VoteType.Against)) + ); + } + + /** + * @dev Proposes a global proposal struct and casts votes by signature. + */ + function _castGlobalProposalBySignatures( + GlobalProposal.GlobalProposalDetail calldata _globalProposal, + Ballot.VoteType[] calldata _supports, + Signature[] calldata _signatures, + bytes32 _domainSeparator, + address _roninTrustedOrganizationContract, + address _gatewayContract + ) internal { + Proposal.ProposalDetail memory _proposal = _globalProposal.into_proposal_detail( + _roninTrustedOrganizationContract, + _gatewayContract + ); + bytes32 _globalProposalHash = _globalProposal.hash(); + require(vote[0][_proposal.nonce].hash == _proposal.hash(), "GovernanceAdmin: cast vote for invalid proposal"); + _castVotesBySignatures( + _proposal, + _supports, + _signatures, + ECDSA.toTypedDataHash(_domainSeparator, Ballot.hash(_globalProposalHash, Ballot.VoteType.For)), + ECDSA.toTypedDataHash(_domainSeparator, Ballot.hash(_globalProposalHash, Ballot.VoteType.Against)) + ); + } + + /** + * @dev Returns the weight of a governor. + */ + function _getWeight(address _governor) internal view virtual returns (uint256); +} diff --git a/contracts/extensions/sequential-governance/GovernanceRelay.sol b/contracts/extensions/sequential-governance/GovernanceRelay.sol new file mode 100644 index 000000000..241042309 --- /dev/null +++ b/contracts/extensions/sequential-governance/GovernanceRelay.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./CoreGovernance.sol"; + +abstract contract GovernanceRelay is CoreGovernance { + using Proposal for Proposal.ProposalDetail; + using GlobalProposal for GlobalProposal.GlobalProposalDetail; + + /** + * @dev Relays votes by signatures. + * + * @notice Does not store the voter signature into storage. + * + */ + function _relayVotesBySignatures( + Proposal.ProposalDetail memory _proposal, + Ballot.VoteType[] calldata _supports, + Signature[] calldata _signatures, + bytes32 _forDigest, + bytes32 _againstDigest + ) internal { + require(_supports.length > 0 && _supports.length == _signatures.length, "GovernanceRelay: invalid array length"); + uint256 _forVoteCount; + uint256 _againstVoteCount; + address[] memory _forVoteSigners = new address[](_signatures.length); + address[] memory _againstVoteSigners = new address[](_signatures.length); + + { + address _signer; + address _lastSigner; + Ballot.VoteType _support; + Signature memory _sig; + + for (uint256 _i; _i < _signatures.length; _i++) { + _sig = _signatures[_i]; + _support = _supports[_i]; + + if (_support == Ballot.VoteType.For) { + _signer = ECDSA.recover(_forDigest, _sig.v, _sig.r, _sig.s); + _forVoteSigners[_forVoteCount++] = _signer; + } else if (_support == Ballot.VoteType.Against) { + _signer = ECDSA.recover(_againstDigest, _sig.v, _sig.r, _sig.s); + _againstVoteSigners[_againstVoteCount++] = _signer; + } else { + revert("GovernanceRelay: query for unsupported vote type"); + } + + require(_lastSigner < _signer, "GovernanceRelay: invalid order"); + _lastSigner = _signer; + } + } + + assembly { + mstore(_forVoteSigners, _forVoteCount) + mstore(_againstVoteSigners, _againstVoteCount) + } + + ProposalVote storage _vote = vote[_proposal.chainId][_proposal.nonce]; + uint256 _minimumForVoteWeight = _getMinimumVoteWeight(); + uint256 _totalForVoteWeight = _getWeights(_forVoteSigners); + if (_totalForVoteWeight >= _minimumForVoteWeight) { + require(_totalForVoteWeight > 0, "GovernanceRelay: invalid vote weight"); + _vote.status = VoteStatus.Approved; + emit ProposalApproved(_vote.hash); + _tryExecute(_vote, _proposal); + return; + } + + uint256 _minimumAgainstVoteWeight = _getTotalWeights() - _minimumForVoteWeight + 1; + uint256 _totalAgainstVoteWeight = _getWeights(_againstVoteSigners); + if (_totalAgainstVoteWeight >= _minimumAgainstVoteWeight) { + require(_totalAgainstVoteWeight > 0, "GovernanceRelay: invalid vote weight"); + _vote.status = VoteStatus.Rejected; + emit ProposalRejected(_vote.hash); + return; + } + + revert("GovernanceRelay: relay failed"); + } + + /** + * @dev Relays voted proposal. + * + * Requirements: + * - The relay proposal is finalized. + * + */ + function _relayProposal( + Proposal.ProposalDetail calldata _proposal, + Ballot.VoteType[] calldata _supports, + Signature[] calldata _signatures, + bytes32 _domainSeparator, + address _creator + ) internal { + _proposeProposalStruct(_proposal, _creator); + bytes32 _proposalHash = _proposal.hash(); + _relayVotesBySignatures( + _proposal, + _supports, + _signatures, + ECDSA.toTypedDataHash(_domainSeparator, Ballot.hash(_proposalHash, Ballot.VoteType.For)), + ECDSA.toTypedDataHash(_domainSeparator, Ballot.hash(_proposalHash, Ballot.VoteType.Against)) + ); + } + + /** + * @dev Relays voted global proposal. + * + * Requirements: + * - The relay proposal is finalized. + * + */ + function _relayGlobalProposal( + GlobalProposal.GlobalProposalDetail calldata _globalProposal, + Ballot.VoteType[] calldata _supports, + Signature[] calldata _signatures, + bytes32 _domainSeparator, + address _roninTrustedOrganizationContract, + address _gatewayContract, + address _creator + ) internal { + (Proposal.ProposalDetail memory _proposal, ) = _proposeGlobalStruct( + _globalProposal, + _roninTrustedOrganizationContract, + _gatewayContract, + _creator + ); + bytes32 _globalProposalHash = _globalProposal.hash(); + _relayVotesBySignatures( + _proposal, + _supports, + _signatures, + ECDSA.toTypedDataHash(_domainSeparator, Ballot.hash(_globalProposalHash, Ballot.VoteType.For)), + ECDSA.toTypedDataHash(_domainSeparator, Ballot.hash(_globalProposalHash, Ballot.VoteType.Against)) + ); + } + + /** + * @dev Returns the weight of the governor list. + */ + function _getWeights(address[] memory _governors) internal view virtual returns (uint256); +} diff --git a/contracts/interfaces/IBridge.sol b/contracts/interfaces/IBridge.sol new file mode 100644 index 000000000..71e81fd2d --- /dev/null +++ b/contracts/interfaces/IBridge.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./consumers/WeightedAddressConsumer.sol"; + +interface IBridge is WeightedAddressConsumer { + /** + * @dev Replaces the old bridge operator list by the new one. + * + * Requirements: + * - The method caller is admin. + * + * Emitted the event `BridgeOperatorsReplaced`. + * + */ + function replaceBridgeOperators(WeightedAddress[] calldata) external; + + /** + * @dev Returns the bridge operator list. + */ + function getBridgeOperators() external view returns (WeightedAddress[] memory); +} diff --git a/contracts/interfaces/IQuorum.sol b/contracts/interfaces/IQuorum.sol new file mode 100644 index 000000000..83706765e --- /dev/null +++ b/contracts/interfaces/IQuorum.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IQuorum { + /// @dev Emitted when the threshold is updated + event ThresholdUpdated( + uint256 indexed nonce, + uint256 indexed numerator, + uint256 indexed denominator, + uint256 previousNumerator, + uint256 previousDenominator + ); + + /** + * @dev Returns the threshold. + */ + function getThreshold() external view returns (uint256 _num, uint256 _denom); + + /** + * @dev Checks whether the `_voteWeight` passes the threshold. + */ + function checkThreshold(uint256 _voteWeight) external view returns (bool); + + /** + * @dev Returns the minimum vote weight to pass the threshold. + */ + function minimumVoteWeight() external view returns (uint256); + + /** + * @dev Sets the threshold. + * + * Requirements: + * - The method caller is admin. + * + * Emits the `ThresholdUpdated` event. + * + */ + function setThreshold(uint256 _numerator, uint256 _denominator) + external + returns (uint256 _previousNum, uint256 _previousDenom); +} diff --git a/contracts/interfaces/IRoninTrustedOrganization.sol b/contracts/interfaces/IRoninTrustedOrganization.sol index 95d579b12..6cca8ee3b 100644 --- a/contracts/interfaces/IRoninTrustedOrganization.sol +++ b/contracts/interfaces/IRoninTrustedOrganization.sol @@ -2,22 +2,40 @@ pragma solidity ^0.8.9; -interface IRoninTrustedOrganization { +import "./consumers/WeightedAddressConsumer.sol"; +import "./IQuorum.sol"; + +interface IRoninTrustedOrganization is WeightedAddressConsumer, IQuorum { /// @dev Emitted when the trusted organization is added. - event TrustedOrganizationAdded(address); + event TrustedOrganizationAdded(WeightedAddress org); + /// @dev Emitted when the trusted organization is updated. + event TrustedOrganizationUpdated(WeightedAddress org); /// @dev Emitted when the trusted organization is removed. - event TrustedOrganizationRemoved(address); + event TrustedOrganizationRemoved(address org); /** * @dev Adds a list of addresses into the trusted organization. * * Requirements: + * - The weights should larger than 0. * - The method caller is admin. * * Emits the event `TrustedOrganizationAdded` once an organization is added. * */ - function addTrustedOrganizations(address[] calldata) external; + function addTrustedOrganizations(WeightedAddress[] calldata) external; + + /** + * @dev Updates weights for a list of existent trusted organization. + * + * Requirements: + * - The weights should larger than 0. + * - The method caller is admin. + * + * Emits the `TrustedOrganizationUpdated` event. + * + */ + function updateTrustedOrganizations(WeightedAddress[] calldata _list) external; /** * @dev Removes a list of addresses from the trusted organization. @@ -31,14 +49,29 @@ interface IRoninTrustedOrganization { function removeTrustedOrganizations(address[] calldata) external; /** - * @dev Returns whether the addresses are trusted organizations. + * @dev Returns total weights. + */ + function totalWeights() external view returns (uint256); + + /** + * @dev Returns the weight of an address. + */ + function getWeight(address _addr) external view returns (uint256); + + /** + * @dev Returns the weights of a list of addresses. + */ + function getWeights(address[] calldata _list) external view returns (uint256[] memory); + + /** + * @dev Returns total weights of the address list. */ - function isTrustedOrganizations(address[] calldata) external view returns (bool[] memory); + function sumWeights(address[] calldata _list) external view returns (uint256 _res); /** * @dev Returns the trusted organization at `_index`. */ - function getTrustedOrganizationAt(uint256 _index) external view returns (address); + function getTrustedOrganizationAt(uint256 _index) external view returns (WeightedAddress memory); /** * @dev Returns the number of trusted organizations. @@ -48,5 +81,5 @@ interface IRoninTrustedOrganization { /** * @dev Returns all of the trusted organization addresses. */ - function getAllTrustedOrganizations() external view returns (address[] memory); + function getAllTrustedOrganizations() external view returns (WeightedAddress[] memory); } diff --git a/contracts/interfaces/collections/IHasBridgeContract.sol b/contracts/interfaces/collections/IHasBridgeContract.sol new file mode 100644 index 000000000..d576a138c --- /dev/null +++ b/contracts/interfaces/collections/IHasBridgeContract.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IHasBridgeContract { + /// @dev Emitted when the bridge contract is updated. + event BridgeContractUpdated(address); + + /** + * @dev Returns the bridge contract. + */ + function bridgeContract() external view returns (address); + + /** + * @dev Sets the bridge contract. + * + * Requirements: + * - The method caller is admin. + * - The new address is a contract. + * + * Emits the event `BridgeContractUpdated`. + * + */ + function setBridgeContract(address) external; +} diff --git a/contracts/interfaces/consumers/SignatureConsumer.sol b/contracts/interfaces/consumers/SignatureConsumer.sol new file mode 100644 index 000000000..91c4989c0 --- /dev/null +++ b/contracts/interfaces/consumers/SignatureConsumer.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface SignatureConsumer { + struct Signature { + uint8 v; + bytes32 r; + bytes32 s; + } +} diff --git a/contracts/interfaces/consumers/VoteStatusConsumer.sol b/contracts/interfaces/consumers/VoteStatusConsumer.sol new file mode 100644 index 000000000..7db10d388 --- /dev/null +++ b/contracts/interfaces/consumers/VoteStatusConsumer.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface VoteStatusConsumer { + enum VoteStatus { + Pending, + Approved, + Executed, + Rejected + } +} diff --git a/contracts/interfaces/consumers/WeightedAddressConsumer.sol b/contracts/interfaces/consumers/WeightedAddressConsumer.sol new file mode 100644 index 000000000..0db9e2a55 --- /dev/null +++ b/contracts/interfaces/consumers/WeightedAddressConsumer.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface WeightedAddressConsumer { + struct WeightedAddress { + address addr; + uint256 weight; + } +} diff --git a/contracts/libraries/Ballot.sol b/contracts/libraries/Ballot.sol new file mode 100644 index 000000000..fce5752a2 --- /dev/null +++ b/contracts/libraries/Ballot.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +library Ballot { + using ECDSA for bytes32; + + enum VoteType { + For, + Against + } + + // keccak256("Ballot(bytes32 proposalHash,uint8 support)"); + bytes32 public constant BALLOT_TYPEHASH = 0xd900570327c4c0df8dd6bdd522b7da7e39145dd049d2fd4602276adcd511e3c2; + + function hash(bytes32 _proposalHash, VoteType _support) internal pure returns (bytes32) { + return keccak256(abi.encode(BALLOT_TYPEHASH, _proposalHash, _support)); + } +} diff --git a/contracts/libraries/BridgeOperatorsBallot.sol b/contracts/libraries/BridgeOperatorsBallot.sol new file mode 100644 index 000000000..0c6bbb3a7 --- /dev/null +++ b/contracts/libraries/BridgeOperatorsBallot.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "../interfaces/consumers/WeightedAddressConsumer.sol"; + +library BridgeOperatorsBallot { + // keccak256("BridgeOperator(address addr,uint256 weight)"); + bytes32 public constant BRIDGE_OPERATOR_TYPEHASH = 0xe71132f1797176c8456299d5325989bbf16523f1e2e3aef4554d23f982955a2c; + + /** + * @dev Returns hash of an operator struct. + */ + function hash(WeightedAddressConsumer.WeightedAddress calldata _operator) internal pure returns (bytes32) { + return keccak256(abi.encode(BRIDGE_OPERATOR_TYPEHASH, _operator.addr, _operator.weight)); + } + + // keccak256("BridgeOperatorsBallot(uint256 period,BridgeOperator[] operators)BridgeOperator(address addr,uint256 weight)"); + bytes32 public constant BRIDGE_OPERATORS_ACKNOWLEDGE_BALLOT_TYPEHASH = + 0x086d287088869477577720f66bf2a8412510e726fd1a893739cf6c2280aadcb5; + + /** + * @dev Returns hash of the ballot. + */ + function hash(uint256 _period, WeightedAddressConsumer.WeightedAddress[] calldata _operators) + internal + pure + returns (bytes32) + { + bytes32[] memory _hashArr = new bytes32[](_operators.length); + for (uint256 _i; _i < _hashArr.length; _i++) { + _hashArr[_i] = hash(_operators[_i]); + } + + bytes32 _operatorsHash; + assembly { + _operatorsHash := keccak256(add(_hashArr, 32), mul(mload(_hashArr), 32)) + } + + return keccak256(abi.encode(BRIDGE_OPERATORS_ACKNOWLEDGE_BALLOT_TYPEHASH, _period, _operatorsHash)); + } +} diff --git a/contracts/libraries/GlobalProposal.sol b/contracts/libraries/GlobalProposal.sol new file mode 100644 index 000000000..a4545bb10 --- /dev/null +++ b/contracts/libraries/GlobalProposal.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./Proposal.sol"; + +library GlobalProposal { + enum TargetOption { + RoninTrustedOrganizationContract, + GatewayContract + } + + struct GlobalProposalDetail { + // Nonce to make sure proposals are executed in order + uint256 nonce; + TargetOption[] targetOptions; + uint256[] values; + bytes[] calldatas; + uint256[] gasAmounts; + } + + // keccak256("GlobalProposalDetail(uint256 nonce,uint8[] targetOptions,uint256[] values,bytes[] calldatas,uint256[] gasAmounts)"); + bytes32 public constant TYPE_HASH = 0xdb316eb400de2ddff92ab4255c0cd3cba634cd5236b93386ed9328b7d822d1c7; + + /** + * @dev Returns struct hash of the proposal. + */ + function hash(GlobalProposalDetail memory _proposal) internal pure returns (bytes32) { + bytes32 _targetsHash; + bytes32 _valuesHash; + bytes32 _calldatasHash; + bytes32 _gasAmountsHash; + + uint256[] memory _values = _proposal.values; + TargetOption[] memory _targets = _proposal.targetOptions; + bytes32[] memory _calldataHashList = new bytes32[](_proposal.calldatas.length); + uint256[] memory _gasAmounts = _proposal.gasAmounts; + + for (uint256 _i; _i < _calldataHashList.length; _i++) { + _calldataHashList[_i] = keccak256(_proposal.calldatas[_i]); + } + + assembly { + _targetsHash := keccak256(add(_targets, 32), mul(mload(_targets), 32)) + _valuesHash := keccak256(add(_values, 32), mul(mload(_values), 32)) + _calldatasHash := keccak256(add(_calldataHashList, 32), mul(mload(_calldataHashList), 32)) + _gasAmountsHash := keccak256(add(_gasAmounts, 32), mul(mload(_gasAmounts), 32)) + } + + return + keccak256(abi.encode(TYPE_HASH, _proposal.nonce, _targetsHash, _valuesHash, _calldatasHash, _gasAmountsHash)); + } + + /** + * @dev Converts into the normal proposal. + */ + function into_proposal_detail( + GlobalProposalDetail memory _proposal, + address _roninTrustedOrganizationContract, + address _gatewayContract + ) internal pure returns (Proposal.ProposalDetail memory _detail) { + _detail.nonce = _proposal.nonce; + _detail.chainId = 0; + _detail.targets = new address[](_proposal.targetOptions.length); + _detail.values = _proposal.values; + _detail.calldatas = _proposal.calldatas; + _detail.gasAmounts = _proposal.gasAmounts; + + for (uint256 _i; _i < _proposal.targetOptions.length; _i++) { + if (_proposal.targetOptions[_i] == TargetOption.GatewayContract) { + _detail.targets[_i] = _gatewayContract; + } else if (_proposal.targetOptions[_i] == TargetOption.RoninTrustedOrganizationContract) { + _detail.targets[_i] = _roninTrustedOrganizationContract; + } else { + revert("GlobalProposal: unsupported target"); + } + } + } +} diff --git a/contracts/libraries/Proposal.sol b/contracts/libraries/Proposal.sol new file mode 100644 index 000000000..eef90360e --- /dev/null +++ b/contracts/libraries/Proposal.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library Proposal { + struct ProposalDetail { + // Nonce to make sure proposals are executed in order + uint256 nonce; + // Value 0: all chain should run this proposal + // Other values: only specifc chain has to execute + uint256 chainId; + address[] targets; + uint256[] values; + bytes[] calldatas; + uint256[] gasAmounts; + } + + // keccak256("ProposalDetail(uint256 nonce,uint256 chainId,address[] targets,uint256[] values,bytes[] calldatas,uint256[] gasAmounts)"); + bytes32 public constant TYPE_HASH = 0x65526afa953b4e935ecd640e6905741252eedae157e79c37331ee8103c70019d; + + /** + * @dev Validates the proposal. + */ + function validate(ProposalDetail memory _proposal) internal pure { + require( + _proposal.targets.length > 0 && + _proposal.targets.length == _proposal.values.length && + _proposal.targets.length == _proposal.calldatas.length && + _proposal.targets.length == _proposal.gasAmounts.length, + "Proposal: invalid array length" + ); + } + + /** + * @dev Returns struct hash of the proposal. + */ + function hash(ProposalDetail memory _proposal) internal pure returns (bytes32) { + bytes32 _targetsHash; + bytes32 _valuesHash; + bytes32 _calldatasHash; + bytes32 _gasAmountsHash; + + uint256[] memory _values = _proposal.values; + address[] memory _targets = _proposal.targets; + bytes32[] memory _calldataHashList = new bytes32[](_proposal.calldatas.length); + uint256[] memory _gasAmounts = _proposal.gasAmounts; + + for (uint256 _i; _i < _calldataHashList.length; _i++) { + _calldataHashList[_i] = keccak256(_proposal.calldatas[_i]); + } + + assembly { + _targetsHash := keccak256(add(_targets, 32), mul(mload(_targets), 32)) + _valuesHash := keccak256(add(_values, 32), mul(mload(_values), 32)) + _calldatasHash := keccak256(add(_calldataHashList, 32), mul(mload(_calldataHashList), 32)) + _gasAmountsHash := keccak256(add(_gasAmounts, 32), mul(mload(_gasAmounts), 32)) + } + + return + keccak256( + abi.encode( + TYPE_HASH, + _proposal.nonce, + _proposal.chainId, + _targetsHash, + _valuesHash, + _calldatasHash, + _gasAmountsHash + ) + ); + } + + /** + * @dev Returns whether the proposal is executable for the current chain. + * + * @notice Does not check whether the call result is successful or not. Please use `execute` instead. + * + */ + function executable(ProposalDetail memory _proposal) internal view returns (bool _result) { + return _proposal.chainId == 0 || _proposal.chainId == block.chainid; + } + + /** + * @dev Executes the proposal. + */ + function execute(ProposalDetail memory _proposal) + internal + returns (bool[] memory _successCalls, bytes[] memory _returnDatas) + { + require(executable(_proposal), "Proposal: query for invalid chainId"); + _successCalls = new bool[](_proposal.targets.length); + _returnDatas = new bytes[](_proposal.targets.length); + for (uint256 _i = 0; _i < _proposal.targets.length; ++_i) { + require(gasleft() > _proposal.gasAmounts[_i], "Proposal: insufficient gas"); + + (_successCalls[_i], _returnDatas[_i]) = _proposal.targets[_i].call{ + value: _proposal.values[_i], + gas: _proposal.gasAmounts[_i] + }(_proposal.calldatas[_i]); + } + } +} diff --git a/contracts/mainchain/MainchainGovernanceAdmin.sol b/contracts/mainchain/MainchainGovernanceAdmin.sol new file mode 100644 index 000000000..764b5e439 --- /dev/null +++ b/contracts/mainchain/MainchainGovernanceAdmin.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; +import "../extensions/isolated-governance/bridge-operator-governance/BOsGovernanceRelay.sol"; +import "../extensions/sequential-governance/GovernanceRelay.sol"; +import "../extensions/GovernanceAdmin.sol"; +import "../interfaces/IBridge.sol"; + +contract MainchainGovernanceAdmin is AccessControlEnumerable, GovernanceRelay, GovernanceAdmin, BOsGovernanceRelay { + bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE"); + + constructor( + address _roleSetter, + address _roninTrustedOrganizationContract, + address _bridgeContract, + address[] memory _relayers + ) GovernanceAdmin(_roninTrustedOrganizationContract, _bridgeContract) { + _setupRole(DEFAULT_ADMIN_ROLE, _roleSetter); + for (uint256 _i; _i < _relayers.length; _i++) { + _grantRole(RELAYER_ROLE, _relayers[_i]); + } + } + + /** + * @dev See {GovernanceRelay-_relayProposal}. + * + * Requirements: + * - The method caller is relayer. + * + */ + function relayProposal( + Proposal.ProposalDetail calldata _proposal, + Ballot.VoteType[] calldata _supports, + Signature[] calldata _signatures + ) external onlyRole(RELAYER_ROLE) { + _relayProposal(_proposal, _supports, _signatures, DOMAIN_SEPARATOR, msg.sender); + } + + /** + * @dev See {GovernanceRelay-_relayGlobalProposal}. + * + * Requirements: + * - The method caller is relayer. + * + */ + function relayGlobalProposal( + GlobalProposal.GlobalProposalDetail calldata _globalProposal, + Ballot.VoteType[] calldata _supports, + Signature[] calldata _signatures + ) external onlyRole(RELAYER_ROLE) { + _relayGlobalProposal( + _globalProposal, + _supports, + _signatures, + DOMAIN_SEPARATOR, + roninTrustedOrganizationContract(), + bridgeContract(), + msg.sender + ); + } + + /** + * @dev See {BOsGovernanceRelay-_relayVotesBySignatures}. + * + * Requirements: + * - The method caller is relayer. + * + */ + function relayBridgeOperators( + uint256 _period, + WeightedAddress[] calldata _operators, + Signature[] calldata _signatures + ) external onlyRole(RELAYER_ROLE) { + _relayVotesBySignatures(_operators, _signatures, _period, _getMinimumVoteWeight(), DOMAIN_SEPARATOR); + _bridgeContract.replaceBridgeOperators(_operators); + } + + /** + * @dev Override {CoreGovernance-_getWeights}. + */ + function _getWeights(address[] memory _governors) + internal + view + virtual + override(BOsGovernanceRelay, GovernanceRelay) + returns (uint256) + { + (bool _success, bytes memory _returndata) = roninTrustedOrganizationContract().staticcall( + abi.encodeWithSelector( + // TransparentUpgradeableProxyV2.functionDelegateCall.selector, + 0x4bb5274a, + abi.encodeWithSelector(IRoninTrustedOrganization.sumWeights.selector, _governors) + ) + ); + require(_success, "GovernanceAdmin: proxy call `sumWeights(address[])` failed"); + return abi.decode(_returndata, (uint256)); + } +} diff --git a/contracts/mocks/MockBridge.sol b/contracts/mocks/MockBridge.sol new file mode 100644 index 000000000..7e18ae5ae --- /dev/null +++ b/contracts/mocks/MockBridge.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../interfaces/IBridge.sol"; + +contract MockBridge is IBridge { + WeightedAddress[] public bridgeOperators; + + function replaceBridgeOperators(WeightedAddress[] calldata _list) external override { + while (bridgeOperators.length > 0) { + bridgeOperators.pop(); + } + for (uint _i = 0; _i < _list.length; _i++) { + bridgeOperators.push(_list[_i]); + } + } + + function getBridgeOperators() external view override returns (WeightedAddress[] memory) { + return bridgeOperators; + } +} diff --git a/contracts/mocks/MockRoninValidatorSetExtended.sol b/contracts/mocks/MockRoninValidatorSetExtended.sol index 53533d2ec..42c14d66c 100644 --- a/contracts/mocks/MockRoninValidatorSetExtended.sol +++ b/contracts/mocks/MockRoninValidatorSetExtended.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.9; -import "../validator/RoninValidatorSet.sol"; import "../libraries/Sorting.sol"; +import "../ronin/validator/RoninValidatorSet.sol"; contract MockRoninValidatorSetExtended is RoninValidatorSet { uint256[] internal _epochs; diff --git a/contracts/mocks/MockRoninValidatorSetSorting.sol b/contracts/mocks/MockRoninValidatorSetSorting.sol index c63141f16..959ca9010 100644 --- a/contracts/mocks/MockRoninValidatorSetSorting.sol +++ b/contracts/mocks/MockRoninValidatorSetSorting.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.9; -import "../validator/RoninValidatorSet.sol"; +import "../ronin/validator/RoninValidatorSet.sol"; import "../libraries/Sorting.sol"; contract MockRoninValidatorSetSorting is RoninValidatorSet { diff --git a/contracts/mocks/MockSlashIndicatorExtended.sol b/contracts/mocks/MockSlashIndicatorExtended.sol index 2dccaeb9a..d191676d1 100644 --- a/contracts/mocks/MockSlashIndicatorExtended.sol +++ b/contracts/mocks/MockSlashIndicatorExtended.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.9; -import "../SlashIndicator.sol"; +import "../ronin/SlashIndicator.sol"; contract MockSlashIndicatorExtended is SlashIndicator { function slashFelony(address _validatorAddr) external { diff --git a/contracts/mocks/MockStaking.sol b/contracts/mocks/MockStaking.sol index f656b0425..56e786d29 100644 --- a/contracts/mocks/MockStaking.sol +++ b/contracts/mocks/MockStaking.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.9; -import "../staking/RewardCalculation.sol"; +import "../ronin/staking/RewardCalculation.sol"; contract MockStaking is RewardCalculation { /// @dev Mapping from user => staking balance diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index cb6c21afb..48365da73 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.9; import "../interfaces/ISlashIndicator.sol"; import "../interfaces/IRoninValidatorSet.sol"; import "../interfaces/IStaking.sol"; -import "../validator/CandidateManager.sol"; +import "../ronin/validator/CandidateManager.sol"; contract MockValidatorSet is IRoninValidatorSet, CandidateManager { address public stakingVestingContract; diff --git a/contracts/multi-chains/RoninTrustedOrganization.sol b/contracts/multi-chains/RoninTrustedOrganization.sol index 032435057..d4803c178 100644 --- a/contracts/multi-chains/RoninTrustedOrganization.sol +++ b/contracts/multi-chains/RoninTrustedOrganization.sol @@ -5,37 +5,98 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "../interfaces/IRoninTrustedOrganization.sol"; -import "../extensions/HasProxyAdmin.sol"; +import "../extensions/collections/HasProxyAdmin.sol"; contract RoninTrustedOrganization is IRoninTrustedOrganization, HasProxyAdmin, Initializable { using EnumerableSet for EnumerableSet.AddressSet; + uint256 internal _num; + uint256 internal _denom; + uint256 internal _totalWeights; + uint256 internal _nonce; + + /// @dev Address set of the trusted organizations EnumerableSet.AddressSet internal _orgs; + /// @dev Mapping from trusted organization address => its weight + mapping(address => uint256) internal _weight; /** * @dev Initializes the contract storage. */ - function initialize(address[] calldata _trustedOrgs) external initializer { + function initialize( + WeightedAddress[] calldata _trustedOrgs, + uint256 __num, + uint256 __denom + ) external initializer { _addTrustedOrganizations(_trustedOrgs); + _setThreshold(__num, __denom); + } + + /** + * @inheritdoc IQuorum + */ + function getThreshold() external view virtual returns (uint256, uint256) { + return (_num, _denom); + } + + /** + * @inheritdoc IQuorum + */ + function checkThreshold(uint256 _voteWeight) external view virtual returns (bool) { + return _voteWeight * _denom >= _num * _totalWeights; + } + + /** + * @inheritdoc IQuorum + */ + function minimumVoteWeight() external view virtual returns (uint256) { + return (_num * _totalWeights + _denom - 1) / _denom; + } + + /** + * @inheritdoc IQuorum + */ + function setThreshold(uint256 _numerator, uint256 _denominator) + external + override + onlyAdmin + returns (uint256, uint256) + { + return _setThreshold(_numerator, _denominator); } /** * @inheritdoc IRoninTrustedOrganization */ - function addTrustedOrganizations(address[] calldata _list) external override onlyAdmin { + function addTrustedOrganizations(WeightedAddress[] calldata _list) external override onlyAdmin { _addTrustedOrganizations(_list); } /** * @inheritdoc IRoninTrustedOrganization */ - function removeTrustedOrganizations(address[] calldata _list) external override onlyAdmin { - if (_list.length == 0) { - return; + function updateTrustedOrganizations(WeightedAddress[] calldata _list) external override onlyAdmin { + WeightedAddress memory _item; + for (uint _i = 0; _i < _list.length; _i++) { + _item = _list[_i]; + + if (_orgs.contains(_item.addr) && _item.weight > 0) { + _totalWeights -= _weight[_item.addr]; + _totalWeights += _item.weight; + _weight[_item.addr] = _item.weight; + emit TrustedOrganizationUpdated(_item); + } } + } + /** + * @inheritdoc IRoninTrustedOrganization + */ + function removeTrustedOrganizations(address[] calldata _list) external override onlyAdmin { for (uint _i = 0; _i < _list.length; _i++) { if (_orgs.remove(_list[_i])) { + _totalWeights -= _weight[_list[_i]]; + delete _weight[_list[_i]]; emit TrustedOrganizationRemoved(_list[_i]); } } @@ -44,18 +105,42 @@ contract RoninTrustedOrganization is IRoninTrustedOrganization, HasProxyAdmin, I /** * @inheritdoc IRoninTrustedOrganization */ - function isTrustedOrganizations(address[] calldata _list) external view override returns (bool[] memory _res) { - _res = new bool[](_list.length); + function totalWeights() external view virtual returns (uint256) { + return _totalWeights; + } + + /** + * @inheritdoc IRoninTrustedOrganization + */ + function getWeight(address _addr) external view override returns (uint256) { + return _weight[_addr]; + } + + /** + * @inheritdoc IRoninTrustedOrganization + */ + function getWeights(address[] calldata _list) external view override returns (uint256[] memory _res) { + _res = new uint256[](_list.length); for (uint _i = 0; _i < _res.length; _i++) { - _res[_i] = _orgs.contains(_list[_i]); + _res[_i] = _weight[_list[_i]]; } } /** * @inheritdoc IRoninTrustedOrganization */ - function getTrustedOrganizationAt(uint256 _idx) external view override returns (address) { - return _orgs.at(_idx); + function sumWeights(address[] calldata _list) external view override returns (uint256 _res) { + for (uint _i = 0; _i < _list.length; _i++) { + _res += _weight[_list[_i]]; + } + } + + /** + * @inheritdoc IRoninTrustedOrganization + */ + function getTrustedOrganizationAt(uint256 _idx) external view override returns (WeightedAddress memory _res) { + _res.addr = _orgs.at(_idx); + _res.weight = _weight[_res.addr]; } /** @@ -68,22 +153,46 @@ contract RoninTrustedOrganization is IRoninTrustedOrganization, HasProxyAdmin, I /** * @inheritdoc IRoninTrustedOrganization */ - function getAllTrustedOrganizations() external view override returns (address[] memory) { - return _orgs.values(); + function getAllTrustedOrganizations() external view override returns (WeightedAddress[] memory _res) { + address[] memory _list = _orgs.values(); + _res = new WeightedAddress[](_list.length); + for (uint _i = 0; _i < _res.length; _i++) { + _res[_i].addr = _list[_i]; + _res[_i].weight = _weight[_list[_i]]; + } } /** * @dev Adds a list of addresses into the trusted organization. */ - function _addTrustedOrganizations(address[] calldata _list) internal { - if (_list.length == 0) { - return; - } - + function _addTrustedOrganizations(WeightedAddress[] calldata _list) internal { for (uint _i = 0; _i < _list.length; _i++) { - if (_orgs.add(_list[_i])) { - emit TrustedOrganizationAdded(_list[_i]); + if (_list[_i].weight > 0) { + if (_orgs.add(_list[_i].addr)) { + _totalWeights += _list[_i].weight; + _weight[_list[_i].addr] = _list[_i].weight; + emit TrustedOrganizationAdded(_list[_i]); + } } } } + + /** + * @dev Sets threshold and returns the old one. + * + * Emits the `ThresholdUpdated` event. + * + */ + function _setThreshold(uint256 _numerator, uint256 _denominator) + internal + virtual + returns (uint256 _previousNum, uint256 _previousDenom) + { + require(_numerator <= _denominator, "RoninTrustedOrganization: invalid threshold"); + _previousNum = _num; + _previousDenom = _denom; + _num = _numerator; + _denom = _denominator; + emit ThresholdUpdated(_nonce++, _numerator, _denominator, _previousNum, _previousDenom); + } } diff --git a/contracts/Maintenance.sol b/contracts/ronin/Maintenance.sol similarity index 96% rename from contracts/Maintenance.sol rename to contracts/ronin/Maintenance.sol index d0b4703b5..5411f5eb9 100644 --- a/contracts/Maintenance.sol +++ b/contracts/ronin/Maintenance.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "./interfaces/IMaintenance.sol"; -import "./interfaces/IRoninValidatorSet.sol"; -import "./interfaces/IStaking.sol"; -import "./extensions/HasValidatorContract.sol"; -import "./libraries/Math.sol"; +import "../interfaces/IMaintenance.sol"; +import "../interfaces/IRoninValidatorSet.sol"; +import "../interfaces/IStaking.sol"; +import "../extensions/collections/HasValidatorContract.sol"; +import "../libraries/Math.sol"; contract Maintenance is IMaintenance, HasValidatorContract, Initializable { using Math for uint256; diff --git a/contracts/ronin/RoninGovernanceAdmin.sol b/contracts/ronin/RoninGovernanceAdmin.sol new file mode 100644 index 000000000..23d422dd6 --- /dev/null +++ b/contracts/ronin/RoninGovernanceAdmin.sol @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../extensions/isolated-governance/bridge-operator-governance/BOsGovernanceProposal.sol"; +import "../extensions/sequential-governance/GovernanceProposal.sol"; +import "../extensions/GovernanceAdmin.sol"; +import "../interfaces/IBridge.sol"; + +contract RoninGovernanceAdmin is GovernanceAdmin, GovernanceProposal, BOsGovernanceProposal { + modifier onlyGovernor() { + require(_getWeight(msg.sender) > 0, "GovernanceAdmin: sender is not governor"); + _; + } + + constructor(address _roninTrustedOrganizationContract, address _bridgeContract) + GovernanceAdmin(_roninTrustedOrganizationContract, _bridgeContract) + {} + + /** + * @dev Returns the voted signatures for the proposals. + * + * Note: Does not verify whether the voter casted vote for the proposal and the returned signature can be empty. + * Please consider filtering for empty signatures after calling this function. + * + */ + function getProposalSignatures( + uint256 _chainId, + uint256 _round, + address[] calldata _voters + ) external view returns (Ballot.VoteType[] memory _supports, Signature[] memory _signatures) { + ProposalVote storage _vote = vote[_chainId][_round]; + + address _voter; + _supports = new Ballot.VoteType[](_voters.length); + _signatures = new Signature[](_voters.length); + for (uint256 _i; _i < _voters.length; _i++) { + _voter = _voters[_i]; + + if (_vote.againstVoted[_voter]) { + _supports[_i] = Ballot.VoteType.Against; + } + + _signatures[_i] = vote[_chainId][_round].sig[_voter]; + } + } + + /** + * @dev Returns the voted signatures for bridge operators at a specific period. + * + * Note: Does not verify whether the voter casted vote for the proposal and the returned signature can be empty. + * Please consider filtering for empty signatures after calling this function. + * + */ + function getBridgeOperatorVotingSignatures(uint256 _period, address[] calldata _voters) + external + view + returns (Signature[] memory _signatures) + { + _signatures = new Signature[](_voters.length); + for (uint256 _i; _i < _voters.length; _i++) { + _signatures[_i] = _votingSig[_period][_voters[_i]]; + } + } + + /** + * @dev See {CoreGovernance-_proposeProposal}. + * + * Requirements: + * - The method caller is governor. + * + */ + function propose( + uint256 _chainId, + address[] calldata _targets, + uint256[] calldata _values, + bytes[] calldata _calldatas, + uint256[] calldata _gasAmounts + ) external onlyGovernor { + _proposeProposal(_chainId, _targets, _values, _calldatas, _gasAmounts, msg.sender); + } + + /** + * @dev See {GovernanceProposal-_proposeProposalStructAndCastVotes}. + * + * Requirements: + * - The method caller is governor. + * + */ + function proposeProposalStructAndCastVotes( + Proposal.ProposalDetail calldata _proposal, + Ballot.VoteType[] calldata _supports, + Signature[] calldata _signatures + ) external onlyGovernor { + _proposeProposalStructAndCastVotes(_proposal, _supports, _signatures, DOMAIN_SEPARATOR, msg.sender); + } + + /** + * @dev See {GovernanceProposal-_castProposalBySignatures}. + */ + function castProposalBySignatures( + Proposal.ProposalDetail calldata _proposal, + Ballot.VoteType[] calldata _supports, + Signature[] calldata _signatures + ) external { + _castProposalBySignatures(_proposal, _supports, _signatures, DOMAIN_SEPARATOR); + } + + /** + * @dev See {CoreGovernance-_proposeGlobal}. + * + * Requirements: + * - The method caller is governor. + * + */ + function proposeGlobal( + GlobalProposal.TargetOption[] calldata _targetOptions, + uint256[] calldata _values, + bytes[] calldata _calldatas, + uint256[] calldata _gasAmounts + ) external onlyGovernor { + _proposeGlobal( + _targetOptions, + _values, + _calldatas, + _gasAmounts, + roninTrustedOrganizationContract(), + bridgeContract(), + msg.sender + ); + } + + /** + * @dev See {GovernanceProposal-_proposeGlobalProposalStructAndCastVotes}. + * + * Requirements: + * - The method caller is governor. + * + */ + function proposeGlobalProposalStructAndCastVotes( + GlobalProposal.GlobalProposalDetail calldata _globalProposal, + Ballot.VoteType[] calldata _supports, + Signature[] calldata _signatures + ) external onlyGovernor { + _proposeGlobalProposalStructAndCastVotes( + _globalProposal, + _supports, + _signatures, + DOMAIN_SEPARATOR, + roninTrustedOrganizationContract(), + bridgeContract(), + msg.sender + ); + } + + /** + * @dev See {GovernanceProposal-_castGlobalProposalBySignatures}. + */ + function castGlobalProposalBySignatures( + GlobalProposal.GlobalProposalDetail calldata _globalProposal, + Ballot.VoteType[] calldata _supports, + Signature[] calldata _signatures + ) external { + _castGlobalProposalBySignatures( + _globalProposal, + _supports, + _signatures, + DOMAIN_SEPARATOR, + roninTrustedOrganizationContract(), + bridgeContract() + ); + } + + /** + * @dev See {BOsGovernanceProposal-_castVotesBySignatures}. + */ + function voteBridgeOperatorsBySignatures( + uint256 _period, + WeightedAddress[] calldata _operators, + Signature[] calldata _signatures + ) external { + _castVotesBySignatures(_operators, _signatures, _period, _getMinimumVoteWeight(), DOMAIN_SEPARATOR); + IsolatedVote storage _v = _vote[_period]; + if (_v.status == VoteStatus.Approved) { + _lastSyncedPeriod = _period; + _v.status = VoteStatus.Executed; + _bridgeContract.replaceBridgeOperators(_operators); + } + } + + /** + * @dev Override {CoreGovernance-_getWeight}. + */ + function _getWeight(address _governor) + internal + view + virtual + override(BOsGovernanceProposal, GovernanceProposal) + returns (uint256) + { + (bool _success, bytes memory _returndata) = roninTrustedOrganizationContract().staticcall( + abi.encodeWithSelector( + // TransparentUpgradeableProxyV2.functionDelegateCall.selector, + 0x4bb5274a, + abi.encodeWithSelector(IRoninTrustedOrganization.getWeight.selector, _governor) + ) + ); + require(_success, "GovernanceAdmin: proxy call `getWeight(address)` failed"); + return abi.decode(_returndata, (uint256)); + } +} diff --git a/contracts/SlashIndicator.sol b/contracts/ronin/SlashIndicator.sol similarity index 97% rename from contracts/SlashIndicator.sol rename to contracts/ronin/SlashIndicator.sol index 8ed25e216..19a4c9604 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/ronin/SlashIndicator.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "./interfaces/ISlashIndicator.sol"; -import "./extensions/HasValidatorContract.sol"; -import "./extensions/HasMaintenanceContract.sol"; -import "./libraries/Math.sol"; -import "./precompile-usages/PrecompileUsageValidateDoubleSign.sol"; +import "../interfaces/ISlashIndicator.sol"; +import "../extensions/collections/HasValidatorContract.sol"; +import "../extensions/collections/HasMaintenanceContract.sol"; +import "../libraries/Math.sol"; +import "../precompile-usages/PrecompileUsageValidateDoubleSign.sol"; contract SlashIndicator is ISlashIndicator, diff --git a/contracts/StakingVesting.sol b/contracts/ronin/StakingVesting.sol similarity index 92% rename from contracts/StakingVesting.sol rename to contracts/ronin/StakingVesting.sol index b6dd5d1fa..7921a7c9c 100644 --- a/contracts/StakingVesting.sol +++ b/contracts/ronin/StakingVesting.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "./interfaces/IStakingVesting.sol"; -import "./extensions/HasValidatorContract.sol"; -import "./extensions/RONTransferHelper.sol"; +import "../interfaces/IStakingVesting.sol"; +import "../extensions/collections/HasValidatorContract.sol"; +import "../extensions/RONTransferHelper.sol"; contract StakingVesting is IStakingVesting, HasValidatorContract, RONTransferHelper, Initializable { /// @dev The block bonus whenever a new block is mined. diff --git a/contracts/staking/RewardCalculation.sol b/contracts/ronin/staking/RewardCalculation.sol similarity index 99% rename from contracts/staking/RewardCalculation.sol rename to contracts/ronin/staking/RewardCalculation.sol index 8aac6e366..ec2ee231c 100644 --- a/contracts/staking/RewardCalculation.sol +++ b/contracts/ronin/staking/RewardCalculation.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.9; -import "../interfaces/IRewardPool.sol"; +import "../../interfaces/IRewardPool.sol"; /** * @title RewardCalculation contract diff --git a/contracts/staking/Staking.sol b/contracts/ronin/staking/Staking.sol similarity index 97% rename from contracts/staking/Staking.sol rename to contracts/ronin/staking/Staking.sol index 0980a3ba6..84b361613 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/ronin/staking/Staking.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "../interfaces/IStaking.sol"; -import "../interfaces/IRoninValidatorSet.sol"; -import "../libraries/Sorting.sol"; +import "../../interfaces/IStaking.sol"; +import "../../interfaces/IRoninValidatorSet.sol"; +import "../../libraries/Sorting.sol"; import "./StakingManager.sol"; contract Staking is IStaking, StakingManager, Initializable { diff --git a/contracts/staking/StakingManager.sol b/contracts/ronin/staking/StakingManager.sol similarity index 98% rename from contracts/staking/StakingManager.sol rename to contracts/ronin/staking/StakingManager.sol index 18d6d1bed..973d65a2f 100644 --- a/contracts/staking/StakingManager.sol +++ b/contracts/ronin/staking/StakingManager.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import "../extensions/RONTransferHelper.sol"; -import "../extensions/HasValidatorContract.sol"; -import "../interfaces/IStaking.sol"; -import "../libraries/Math.sol"; +import "../../extensions/RONTransferHelper.sol"; +import "../../extensions/collections/HasValidatorContract.sol"; +import "../../interfaces/IStaking.sol"; +import "../../libraries/Math.sol"; import "./RewardCalculation.sol"; abstract contract StakingManager is diff --git a/contracts/validator/CandidateManager.sol b/contracts/ronin/validator/CandidateManager.sol similarity index 96% rename from contracts/validator/CandidateManager.sol rename to contracts/ronin/validator/CandidateManager.sol index 8aa6593bc..8fa7cd706 100644 --- a/contracts/validator/CandidateManager.sol +++ b/contracts/ronin/validator/CandidateManager.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.9; -import "../extensions/HasStakingContract.sol"; -import "../interfaces/ICandidateManager.sol"; -import "../interfaces/IStaking.sol"; -import "../libraries/Sorting.sol"; +import "../../extensions/collections/HasStakingContract.sol"; +import "../../interfaces/ICandidateManager.sol"; +import "../../interfaces/IStaking.sol"; +import "../../libraries/Sorting.sol"; abstract contract CandidateManager is ICandidateManager, HasStakingContract { /// @dev Maximum number of validator candidate diff --git a/contracts/validator/RoninValidatorSet.sol b/contracts/ronin/validator/RoninValidatorSet.sol similarity index 95% rename from contracts/validator/RoninValidatorSet.sol rename to contracts/ronin/validator/RoninValidatorSet.sol index 346a3b391..3dcb5d0c9 100644 --- a/contracts/validator/RoninValidatorSet.sol +++ b/contracts/ronin/validator/RoninValidatorSet.sol @@ -4,15 +4,16 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import "../extensions/RONTransferHelper.sol"; -import "../extensions/HasStakingVestingContract.sol"; -import "../extensions/HasStakingContract.sol"; -import "../extensions/HasSlashIndicatorContract.sol"; -import "../extensions/HasMaintenanceContract.sol"; -import "../extensions/HasRoninTrustedOrganizationContract.sol"; -import "../interfaces/IRoninValidatorSet.sol"; -import "../libraries/Math.sol"; -import "../precompile-usages/PrecompileUsageSortValidators.sol"; +import "../../extensions/RONTransferHelper.sol"; +import "../../extensions/collections/HasStakingVestingContract.sol"; +import "../../extensions/collections/HasStakingContract.sol"; +import "../../extensions/collections/HasSlashIndicatorContract.sol"; +import "../../extensions/collections/HasMaintenanceContract.sol"; +import "../../extensions/collections/HasRoninTrustedOrganizationContract.sol"; +import "../../interfaces/IRoninValidatorSet.sol"; +import "../../libraries/Sorting.sol"; +import "../../libraries/Math.sol"; +import "../../precompile-usages/PrecompileUsageSortValidators.sol"; import "./CandidateManager.sol"; contract RoninValidatorSet is @@ -477,9 +478,9 @@ contract RoninValidatorSet is uint _waitingCounter; uint _prioritySlotCounter; - bool[] memory _isTrustedOrgs = _roninTrustedOrganizationContract.isTrustedOrganizations(_candidates); + uint256[] memory _trustedWeights = _roninTrustedOrganizationContract.getWeights(_candidates); for (uint _i = 0; _i < _candidates.length; _i++) { - if (_isTrustedOrgs[_i] && _prioritySlotCounter < _maxPrioritizedValidatorNumber) { + if (_trustedWeights[_i] > 0 && _prioritySlotCounter < _maxPrioritizedValidatorNumber) { _candidates[_prioritySlotCounter++] = _candidates[_i]; continue; } diff --git a/src/config.ts b/src/config.ts index d272f3cde..82d3df393 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,7 @@ import { BigNumber, BigNumberish } from 'ethers'; import { ethers } from 'hardhat'; import { Address } from 'hardhat-deploy/dist/types'; +import { WeightedAddressStruct } from './types/IBridge'; export enum Network { Hardhat = 'hardhat', @@ -31,7 +32,7 @@ export interface AddressExtended { export interface InitAddr { [network: LiteralNetwork]: { - governanceAdmin: Address; + governanceAdmin?: AddressExtended; maintenanceContract?: AddressExtended; stakingVestingContract?: AddressExtended; slashIndicatorContract?: AddressExtended; @@ -49,7 +50,9 @@ export interface MaintenanceArguments { } export interface RoninTrustedOrganizationArguments { - trustedOrganizations?: Address[]; + trustedOrganizations?: WeightedAddressStruct[]; + numerator?: BigNumberish; + denominator?: BigNumberish; } export interface RoninTrustedOrganizationConfig { @@ -102,15 +105,26 @@ export interface RoninValidatorSetConfig { [network: LiteralNetwork]: RoninValidatorSetArguments | undefined; } -export const initAddress: InitAddr = { - [Network.Hardhat]: { - governanceAdmin: ethers.constants.AddressZero, - }, - [Network.Devnet]: { - governanceAdmin: '0x93b8eed0a1e082ae2f478fd7f8c14b1fc0261bb1', - }, +export interface RoninGovernanceAdminArguments { + bridgeContract?: Address; +} + +export interface RoninGovernanceAdminConfig { + [network: LiteralNetwork]: RoninGovernanceAdminArguments | undefined; +} + +export type MainchainGovernanceAdminArguments = RoninGovernanceAdminArguments & { + roleSetter?: Address; + relayers?: Address[]; }; +export interface MainchainGovernanceAdminConfig { + [network: LiteralNetwork]: MainchainGovernanceAdminArguments | undefined; +} + +export const roninInitAddress: InitAddr = {}; +export const mainchainInitAddress: InitAddr = {}; + // TODO: update config for testnet & mainnet export const maintenanceConf: MaintenanceConfig = { [Network.Hardhat]: undefined, @@ -178,10 +192,34 @@ export const roninValidatorSetConf: RoninValidatorSetConfig = { export const roninTrustedOrganizationConf: RoninTrustedOrganizationConfig = { [Network.Hardhat]: undefined, [Network.Devnet]: { - trustedOrganizations: [], // trusted no one + trustedOrganizations: ['0x93b8eed0a1e082ae2f478fd7f8c14b1fc0261bb1'].map((addr) => ({ addr, weight: 100 })), + numerator: 1, + denominator: 1, }, [Network.Testnet]: undefined, [Network.Mainnet]: undefined, [Network.Goerli]: undefined, [Network.Ethereum]: undefined, }; + +// TODO: update config for testnet & mainnet +export const roninGovernanceAdminConf: RoninGovernanceAdminConfig = { + [Network.Hardhat]: undefined, + [Network.Devnet]: { + bridgeContract: ethers.constants.AddressZero, + }, + [Network.Goerli]: undefined, + [Network.Ethereum]: undefined, +}; + +// TODO: update config for goerli, ethereum +export const mainchainGovernanceAdminConf: MainchainGovernanceAdminConfig = { + [Network.Hardhat]: undefined, + [Network.Devnet]: { + roleSetter: '0x93b8eed0a1e082ae2f478fd7f8c14b1fc0261bb1', + bridgeContract: ethers.constants.AddressZero, + relayers: ['0x93b8eed0a1e082ae2f478fd7f8c14b1fc0261bb1'], + }, + [Network.Goerli]: undefined, + [Network.Ethereum]: undefined, +}; diff --git a/src/deploy/calculate-address.ts b/src/deploy/calculate-address.ts index aa6a881cb..bdc9548d0 100644 --- a/src/deploy/calculate-address.ts +++ b/src/deploy/calculate-address.ts @@ -1,7 +1,7 @@ import { ethers, network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { allNetworks, initAddress, roninchainNetworks } from '../config'; +import { roninInitAddress, roninchainNetworks, mainchainNetworks, mainchainInitAddress } from '../config'; const calculateAddress = (from: string, nonce: number) => ({ nonce, @@ -13,11 +13,22 @@ const deploy = async ({ getNamedAccounts }: HardhatRuntimeEnvironment) => { let nonce = await ethers.provider.getTransactionCount(deployer); if (roninchainNetworks.includes(network.name!)) { - initAddress[network.name].maintenanceContract = calculateAddress(deployer, nonce++); - initAddress[network.name].stakingVestingContract = calculateAddress(deployer, nonce++); - initAddress[network.name].slashIndicatorContract = calculateAddress(deployer, nonce++); - initAddress[network.name].stakingContract = calculateAddress(deployer, nonce++); - initAddress[network.name].validatorContract = calculateAddress(deployer, nonce++); + roninInitAddress[network.name] = { + governanceAdmin: calculateAddress(deployer, nonce++), + roninTrustedOrganizationContract: calculateAddress(deployer, nonce++), + maintenanceContract: calculateAddress(deployer, nonce++), + stakingVestingContract: calculateAddress(deployer, nonce++), + slashIndicatorContract: calculateAddress(deployer, nonce++), + stakingContract: calculateAddress(deployer, nonce++), + validatorContract: calculateAddress(deployer, nonce++), + }; + } + + if (mainchainNetworks.includes(network.name!)) { + mainchainInitAddress[network.name] = { + governanceAdmin: calculateAddress(deployer, nonce++), + roninTrustedOrganizationContract: calculateAddress(deployer, nonce++), + }; } }; @@ -29,7 +40,6 @@ deploy.dependencies = [ 'StakingLogic', 'RoninValidatorSetLogic', 'RoninTrustedOrganizationLogic', - 'RoninTrustedOrganizationProxy', ]; export default deploy; diff --git a/src/deploy/logic/slash-indicator.ts b/src/deploy/logic/slash-indicator.ts index f9d94a3fb..13ed52bb5 100644 --- a/src/deploy/logic/slash-indicator.ts +++ b/src/deploy/logic/slash-indicator.ts @@ -1,5 +1,6 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; + import { roninchainNetworks } from '../../config'; const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { diff --git a/src/deploy/logic/staking-vesting.ts b/src/deploy/logic/staking-vesting.ts index 36d492730..1b09c9bd0 100644 --- a/src/deploy/logic/staking-vesting.ts +++ b/src/deploy/logic/staking-vesting.ts @@ -1,5 +1,6 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; + import { roninchainNetworks } from '../../config'; const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { diff --git a/src/deploy/mainchain-governance-admin.ts b/src/deploy/mainchain-governance-admin.ts new file mode 100644 index 000000000..b02e60625 --- /dev/null +++ b/src/deploy/mainchain-governance-admin.ts @@ -0,0 +1,32 @@ +import { network } from 'hardhat'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +import { mainchainGovernanceAdminConf, mainchainInitAddress, mainchainNetworks } from '../config'; +import { verifyAddress } from '../script/verify-address'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!mainchainNetworks.includes(network.name!)) { + return; + } + + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + const deployment = await deploy('MainchainGovernanceAdmin', { + from: deployer, + log: true, + args: [ + mainchainGovernanceAdminConf[network.name]?.roleSetter, + mainchainInitAddress[network.name].roninTrustedOrganizationContract?.address, + mainchainGovernanceAdminConf[network.name]?.bridgeContract, + mainchainGovernanceAdminConf[network.name]?.relayers, + ], + nonce: mainchainInitAddress[network.name].governanceAdmin?.nonce, + }); + verifyAddress(deployment.address, mainchainInitAddress[network.name].governanceAdmin?.address); +}; + +deploy.tags = ['MainchainGovernanceAdmin']; +deploy.dependencies = ['RoninValidatorSetProxy']; + +export default deploy; diff --git a/src/deploy/proxy/mainchain-ronin-trusted-organization-proxy.ts b/src/deploy/proxy/mainchain-ronin-trusted-organization-proxy.ts new file mode 100644 index 000000000..ac67e9335 --- /dev/null +++ b/src/deploy/proxy/mainchain-ronin-trusted-organization-proxy.ts @@ -0,0 +1,36 @@ +import { network } from 'hardhat'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +import { roninTrustedOrganizationConf, mainchainNetworks, mainchainInitAddress } from '../../config'; +import { verifyAddress } from '../../script/verify-address'; +import { RoninTrustedOrganization__factory } from '../../types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!mainchainNetworks.includes(network.name!)) { + return; + } + + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + const logicContract = await deployments.get('RoninTrustedOrganizationLogic'); + const data = new RoninTrustedOrganization__factory().interface.encodeFunctionData('initialize', [ + roninTrustedOrganizationConf[network.name]!.trustedOrganizations, + roninTrustedOrganizationConf[network.name]!.numerator, + roninTrustedOrganizationConf[network.name]!.denominator, + ]); + + const deployment = await deploy('MainchainRoninTrustedOrganizationProxy', { + contract: 'TransparentUpgradeableProxyV2', + from: deployer, + log: true, + args: [logicContract.address, mainchainInitAddress[network.name].governanceAdmin?.address, data], + nonce: mainchainInitAddress[network.name].roninTrustedOrganizationContract?.nonce, + }); + verifyAddress(deployment.address, mainchainInitAddress[network.name].roninTrustedOrganizationContract?.address); +}; + +deploy.tags = ['MainchainRoninTrustedOrganizationProxy']; +deploy.dependencies = ['RoninTrustedOrganizationLogic', 'MainchainGovernanceAdmin']; + +export default deploy; diff --git a/src/deploy/proxy/maintenance-proxy.ts b/src/deploy/proxy/maintenance-proxy.ts index 91e3a1634..4244c25f3 100644 --- a/src/deploy/proxy/maintenance-proxy.ts +++ b/src/deploy/proxy/maintenance-proxy.ts @@ -1,7 +1,7 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { maintenanceConf, initAddress, roninchainNetworks } from '../../config'; +import { maintenanceConf, roninInitAddress, roninchainNetworks } from '../../config'; import { verifyAddress } from '../../script/verify-address'; import { Maintenance__factory } from '../../types'; @@ -16,7 +16,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const logicContract = await deployments.get('MaintenanceLogic'); const data = new Maintenance__factory().interface.encodeFunctionData('initialize', [ - initAddress[network.name]!.validatorContract?.address, + roninInitAddress[network.name]!.validatorContract?.address, maintenanceConf[network.name]!.minMaintenanceBlockPeriod, maintenanceConf[network.name]!.maxMaintenanceBlockPeriod, maintenanceConf[network.name]!.minOffset, @@ -27,13 +27,13 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], - nonce: initAddress[network.name].maintenanceContract?.nonce, + args: [logicContract.address, roninInitAddress[network.name]!.governanceAdmin?.address, data], + nonce: roninInitAddress[network.name].maintenanceContract?.nonce, }); - verifyAddress(deployment.address, initAddress[network.name].maintenanceContract?.address); + verifyAddress(deployment.address, roninInitAddress[network.name].maintenanceContract?.address); }; deploy.tags = ['MaintenanceProxy']; -deploy.dependencies = ['MaintenanceLogic', 'CalculateAddresses']; +deploy.dependencies = ['MaintenanceLogic', 'CalculateAddresses', 'RoninTrustedOrganizationProxy']; export default deploy; diff --git a/src/deploy/proxy/ronin-trusted-organization-proxy.ts b/src/deploy/proxy/ronin-trusted-organization-proxy.ts index 1fbdbacef..b1cac63d7 100644 --- a/src/deploy/proxy/ronin-trusted-organization-proxy.ts +++ b/src/deploy/proxy/ronin-trusted-organization-proxy.ts @@ -1,11 +1,12 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { roninTrustedOrganizationConf, initAddress, allNetworks } from '../../config'; +import { roninTrustedOrganizationConf, roninInitAddress, roninchainNetworks } from '../../config'; +import { verifyAddress } from '../../script/verify-address'; import { RoninTrustedOrganization__factory } from '../../types'; const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { - if (!allNetworks.includes(network.name!)) { + if (!roninchainNetworks.includes(network.name!)) { return; } @@ -13,21 +14,22 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const { deployer } = await getNamedAccounts(); const logicContract = await deployments.get('RoninTrustedOrganizationLogic'); - const data = new RoninTrustedOrganization__factory().interface.encodeFunctionData('initialize', [ roninTrustedOrganizationConf[network.name]!.trustedOrganizations, + roninTrustedOrganizationConf[network.name]!.numerator, + roninTrustedOrganizationConf[network.name]!.denominator, ]); const deployment = await deploy('RoninTrustedOrganizationProxy', { contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], + args: [logicContract.address, roninInitAddress[network.name].governanceAdmin?.address, data], }); - initAddress[network.name].roninTrustedOrganizationContract = { address: deployment.address }; + verifyAddress(deployment.address, roninInitAddress[network.name].roninTrustedOrganizationContract?.address); }; deploy.tags = ['RoninTrustedOrganizationProxy']; -deploy.dependencies = ['RoninTrustedOrganizationLogic']; +deploy.dependencies = ['RoninTrustedOrganizationLogic', 'CalculateAddresses', 'RoninGovernanceAdmin']; export default deploy; diff --git a/src/deploy/proxy/ronin-validator-proxy.ts b/src/deploy/proxy/ronin-validator-proxy.ts index 1084a607d..5c6324b83 100644 --- a/src/deploy/proxy/ronin-validator-proxy.ts +++ b/src/deploy/proxy/ronin-validator-proxy.ts @@ -1,7 +1,7 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { roninValidatorSetConf, initAddress, roninchainNetworks } from '../../config'; +import { roninValidatorSetConf, roninInitAddress, roninchainNetworks } from '../../config'; import { verifyAddress } from '../../script/verify-address'; import { RoninValidatorSet__factory } from '../../types'; @@ -16,11 +16,11 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const logicContract = await deployments.get('RoninValidatorSetLogic'); const data = new RoninValidatorSet__factory().interface.encodeFunctionData('initialize', [ - initAddress[network.name]!.slashIndicatorContract?.address, - initAddress[network.name]!.stakingContract?.address, - initAddress[network.name]!.stakingVestingContract?.address, - initAddress[network.name]!.maintenanceContract?.address, - initAddress[network.name]!.roninTrustedOrganizationContract?.address, + roninInitAddress[network.name]!.slashIndicatorContract?.address, + roninInitAddress[network.name]!.stakingContract?.address, + roninInitAddress[network.name]!.stakingVestingContract?.address, + roninInitAddress[network.name]!.maintenanceContract?.address, + roninInitAddress[network.name]!.roninTrustedOrganizationContract?.address, roninValidatorSetConf[network.name]!.maxValidatorNumber, roninValidatorSetConf[network.name]!.maxValidatorCandidate, roninValidatorSetConf[network.name]!.maxPrioritizedValidatorNumber, @@ -32,10 +32,10 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], - nonce: initAddress[network.name].validatorContract?.nonce, + args: [logicContract.address, roninInitAddress[network.name]!.governanceAdmin?.address, data], + nonce: roninInitAddress[network.name].validatorContract?.nonce, }); - verifyAddress(deployment.address, initAddress[network.name].validatorContract?.address); + verifyAddress(deployment.address, roninInitAddress[network.name].validatorContract?.address); }; deploy.tags = ['RoninValidatorSetProxy']; diff --git a/src/deploy/proxy/slash-indicator-proxy.ts b/src/deploy/proxy/slash-indicator-proxy.ts index 13932c401..24989e6e7 100644 --- a/src/deploy/proxy/slash-indicator-proxy.ts +++ b/src/deploy/proxy/slash-indicator-proxy.ts @@ -1,7 +1,7 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { slashIndicatorConf, initAddress, roninchainNetworks } from '../../config'; +import { slashIndicatorConf, roninInitAddress, roninchainNetworks } from '../../config'; import { verifyAddress } from '../../script/verify-address'; import { SlashIndicator__factory } from '../../types'; @@ -16,8 +16,8 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const logicContract = await deployments.get('SlashIndicatorLogic'); const data = new SlashIndicator__factory().interface.encodeFunctionData('initialize', [ - initAddress[network.name]!.validatorContract?.address, - initAddress[network.name]!.maintenanceContract?.address, + roninInitAddress[network.name]!.validatorContract?.address, + roninInitAddress[network.name]!.maintenanceContract?.address, slashIndicatorConf[network.name]!.misdemeanorThreshold, slashIndicatorConf[network.name]!.felonyThreshold, slashIndicatorConf[network.name]!.slashFelonyAmount, @@ -30,10 +30,10 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], - nonce: initAddress[network.name].slashIndicatorContract?.nonce, + args: [logicContract.address, roninInitAddress[network.name]!.governanceAdmin?.address, data], + nonce: roninInitAddress[network.name].slashIndicatorContract?.nonce, }); - verifyAddress(deployment.address, initAddress[network.name].slashIndicatorContract?.address); + verifyAddress(deployment.address, roninInitAddress[network.name].slashIndicatorContract?.address); }; deploy.tags = ['SlashIndicatorProxy']; diff --git a/src/deploy/proxy/staking-proxy.ts b/src/deploy/proxy/staking-proxy.ts index 3a0002db2..17c4504ac 100644 --- a/src/deploy/proxy/staking-proxy.ts +++ b/src/deploy/proxy/staking-proxy.ts @@ -1,7 +1,7 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { stakingConfig, initAddress, roninchainNetworks } from '../../config'; +import { stakingConfig, roninInitAddress, roninchainNetworks } from '../../config'; import { verifyAddress } from '../../script/verify-address'; import { Staking__factory } from '../../types'; @@ -16,7 +16,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const logicContract = await deployments.get('StakingLogic'); const data = new Staking__factory().interface.encodeFunctionData('initialize', [ - initAddress[network.name]!.validatorContract?.address, + roninInitAddress[network.name]!.validatorContract?.address, stakingConfig[network.name]!.minValidatorBalance, ]); @@ -24,10 +24,10 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], - nonce: initAddress[network.name].stakingContract?.nonce, + args: [logicContract.address, roninInitAddress[network.name]!.governanceAdmin?.address, data], + nonce: roninInitAddress[network.name].stakingContract?.nonce, }); - verifyAddress(deployment.address, initAddress[network.name].stakingContract?.address); + verifyAddress(deployment.address, roninInitAddress[network.name].stakingContract?.address); }; deploy.tags = ['StakingProxy']; diff --git a/src/deploy/proxy/staking-vesting-proxy.ts b/src/deploy/proxy/staking-vesting-proxy.ts index 887165c2e..bb945f850 100644 --- a/src/deploy/proxy/staking-vesting-proxy.ts +++ b/src/deploy/proxy/staking-vesting-proxy.ts @@ -2,7 +2,7 @@ import { BigNumber } from 'ethers'; import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { initAddress, roninchainNetworks, stakingVestingConfig } from '../../config'; +import { roninInitAddress, roninchainNetworks, stakingVestingConfig } from '../../config'; import { verifyAddress } from '../../script/verify-address'; import { StakingVesting__factory } from '../../types'; @@ -17,7 +17,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const logicContract = await deployments.get('StakingVestingLogic'); const data = new StakingVesting__factory().interface.encodeFunctionData('initialize', [ - initAddress[network.name]!.validatorContract?.address, + roninInitAddress[network.name]!.validatorContract?.address, stakingVestingConfig[network.name]!.bonusPerBlock, ]); @@ -25,11 +25,11 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, initAddress[network.name]!.governanceAdmin, data], + args: [logicContract.address, roninInitAddress[network.name]!.governanceAdmin?.address, data], value: BigNumber.from(stakingVestingConfig[network.name]!.topupAmount), - nonce: initAddress[network.name].stakingVestingContract?.nonce, + nonce: roninInitAddress[network.name].stakingVestingContract?.nonce, }); - verifyAddress(deployment.address, initAddress[network.name].stakingVestingContract?.address); + verifyAddress(deployment.address, roninInitAddress[network.name].stakingVestingContract?.address); }; deploy.tags = ['StakingVestingProxy']; diff --git a/src/deploy/ronin-governance-admin.ts b/src/deploy/ronin-governance-admin.ts new file mode 100644 index 000000000..95157e663 --- /dev/null +++ b/src/deploy/ronin-governance-admin.ts @@ -0,0 +1,30 @@ +import { network } from 'hardhat'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +import { roninGovernanceAdminConf, roninchainNetworks, roninInitAddress } from '../config'; +import { verifyAddress } from '../script/verify-address'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!roninchainNetworks.includes(network.name!)) { + return; + } + + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + const deployment = await deploy('RoninGovernanceAdmin', { + from: deployer, + log: true, + args: [ + roninInitAddress[network.name].roninTrustedOrganizationContract?.address, + roninGovernanceAdminConf[network.name]?.bridgeContract, + ], + nonce: roninInitAddress[network.name].governanceAdmin?.nonce, + }); + verifyAddress(deployment.address, roninInitAddress[network.name].governanceAdmin?.address); +}; + +deploy.tags = ['RoninGovernanceAdmin']; +deploy.dependencies = ['CalculateAddresses']; + +export default deploy; diff --git a/src/script/governance-admin-interface.ts b/src/script/governance-admin-interface.ts new file mode 100644 index 000000000..662117661 --- /dev/null +++ b/src/script/governance-admin-interface.ts @@ -0,0 +1,81 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { BigNumberish, BytesLike } from 'ethers'; +import { ethers, network } from 'hardhat'; +import { Address } from 'hardhat-deploy/dist/types'; +import { TypedDataDomain } from '@ethersproject/abstract-signer'; +import { AbiCoder, Interface, keccak256, _TypedDataEncoder } from 'ethers/lib/utils'; + +import { BallotTypes, getProposalHash, VoteType } from './proposal'; +import { RoninGovernanceAdmin, TransparentUpgradeableProxyV2__factory } from '../types'; +import { ProposalDetailStruct } from '../types/GovernanceAdmin'; +import { SignatureStruct } from '../types/MainchainGovernanceAdmin'; + +export const getGovernanceAdminDomain = (): TypedDataDomain => ({ + name: 'GovernanceAdmin', + version: '1', + salt: keccak256(AbiCoder.prototype.encode(['string', 'uint256'], ['RONIN_GOVERNANCE_ADMIN', 2020])), +}); + +export const calculateGovernanceAdminDomainSeparator = () => _TypedDataEncoder.hashDomain(getGovernanceAdminDomain()); + +export const mapByteSigToSigStruct = (sig: string): SignatureStruct => { + const { v, r, s } = ethers.utils.splitSignature(sig); + return { v, r, s }; +}; + +export class GovernanceAdminInterface { + signers!: SignerWithAddress[]; + contract!: RoninGovernanceAdmin; + domain!: TypedDataDomain; + interface!: Interface; + address = ethers.constants.AddressZero; + + constructor(contract: RoninGovernanceAdmin, ...signers: SignerWithAddress[]) { + this.contract = contract; + this.signers = signers; + this.address = contract.address; + this.domain = getGovernanceAdminDomain(); + this.interface = new TransparentUpgradeableProxyV2__factory().interface; + } + + async createProposal(target: Address, value: BigNumberish, calldata: BytesLike, gasAmount: BigNumberish) { + const proposal: ProposalDetailStruct = { + chainId: network.config.chainId!, + nonce: (await this.contract.round(network.config.chainId!)).add(1), + targets: [target], + values: [value], + calldatas: [calldata], + gasAmounts: [gasAmount], + }; + return proposal; + } + + async generateSignatures(proposal: ProposalDetailStruct) { + const proposalHash = getProposalHash(proposal); + const signatures = await Promise.all( + this.signers.map((v) => + v._signTypedData(this.domain, BallotTypes, { proposalHash, support: VoteType.For }).then(mapByteSigToSigStruct) + ) + ); + return signatures; + } + + async functionDelegateCall(to: Address, data: BytesLike) { + const proposal = await this.createProposal( + to, + 0, + this.interface.encodeFunctionData('functionDelegateCall', [data]), + 1_000_000 + ); + const signatures = await this.generateSignatures(proposal); + const supports = signatures.map(() => VoteType.For); + return this.contract.connect(this.signers[0]).proposeProposalStructAndCastVotes(proposal, supports, signatures); + } + + async upgrade(from: Address, to: Address) { + const proposal = await this.createProposal(from, 0, this.interface.encodeFunctionData('upgradeTo', [to]), 500_000); + const signatures = await this.generateSignatures(proposal); + const supports = signatures.map(() => VoteType.For); + return this.contract.connect(this.signers[0]).proposeProposalStructAndCastVotes(proposal, supports, signatures); + } +} diff --git a/src/script/proposal.ts b/src/script/proposal.ts new file mode 100644 index 000000000..23988c771 --- /dev/null +++ b/src/script/proposal.ts @@ -0,0 +1,175 @@ +import { BigNumberish } from 'ethers'; +import { AbiCoder, keccak256, solidityKeccak256 } from 'ethers/lib/utils'; +import { GlobalProposalDetailStruct, ProposalDetailStruct } from '../types/GovernanceAdmin'; +import { WeightedAddressStruct } from '../types/IBridge'; + +// keccak256("ProposalDetail(uint256 nonce,uint256 chainId,address[] targets,uint256[] values,bytes[] calldatas,uint256[] gasAmounts)") +const proposalTypeHash = '0x65526afa953b4e935ecd640e6905741252eedae157e79c37331ee8103c70019d'; +// keccak256("GlobalProposalDetail(uint256 nonce,uint8[] targetOptions,uint256[] values,bytes[] calldatas,uint256[] gasAmounts)") +const globalProposalTypeHash = '0xdb316eb400de2ddff92ab4255c0cd3cba634cd5236b93386ed9328b7d822d1c7'; +// keccak256("Ballot(bytes32 proposalHash,uint8 support)") +const ballotTypeHash = '0xd900570327c4c0df8dd6bdd522b7da7e39145dd049d2fd4602276adcd511e3c2'; +// keccak256("BridgeOperatorsBallot(uint256 period,BridgeOperator[] operators)BridgeOperator(address addr,uint256 weight)"); +const bridgeOperatorsBallotTypeHash = '0x086d287088869477577720f66bf2a8412510e726fd1a893739cf6c2280aadcb5'; +// keccak256("BridgeOperator(address addr,uint256 weight)"); +const bridgeOperatorTypeHash = '0xe71132f1797176c8456299d5325989bbf16523f1e2e3aef4554d23f982955a2c'; + +export enum VoteType { + For = 0, + Against = 1, +} + +export enum VoteStatus { + Pending = 0, + Approved = 1, + Executed = 2, + Rejected = 3, +} + +export const ballotParamTypes = ['bytes32', 'bytes32', 'uint8']; +export const proposalParamTypes = ['bytes32', 'uint256', 'uint256', 'bytes32', 'bytes32', 'bytes32', 'bytes32']; +export const globalProposalParamTypes = ['bytes32', 'uint256', 'bytes32', 'bytes32', 'bytes32', 'bytes32']; +export const bridgeOperatorsBallotParamTypes = ['bytes32', 'uint256', 'bytes32']; +export const bridgeOperatorParamTypes = ['bytes32', 'address', 'uint256']; + +export const BallotTypes = { + Ballot: [ + { name: 'proposalHash', type: 'bytes32' }, + { name: 'support', type: 'uint8' }, + ], +}; + +export const ProposalDetailTypes = { + ProposalDetail: [ + { name: 'chainId', type: 'uint256' }, + { name: 'targets', type: 'address[]' }, + { name: 'values', type: 'uint256[]' }, + { name: 'calldatas', type: 'bytes[]' }, + { name: 'gasAmounts', type: 'uint256[]' }, + ], +}; + +export const GlobalProposalTypes = { + GlobalProposalDetail: [ + { name: 'targetOptions', type: 'uint8[]' }, + { name: 'values', type: 'uint256[]' }, + { name: 'calldatas', type: 'bytes[]' }, + { name: 'gasAmounts', type: 'uint256[]' }, + ], +}; + +export const BridgeOperatorsBallotTypes = { + BridgeOperatorsBallot: [ + { name: 'period', type: 'uint256' }, + { name: 'operators', type: 'BridgeOperator[]' }, + ], + BridgeOperator: [ + { name: 'addr', type: 'address' }, + { name: 'weight', type: 'uint256' }, + ], +}; + +export const getProposalHash = (proposal: ProposalDetailStruct) => + keccak256( + AbiCoder.prototype.encode(proposalParamTypes, [ + proposalTypeHash, + proposal.nonce, + proposal.chainId, + keccak256( + AbiCoder.prototype.encode( + proposal.targets.map(() => 'address'), + proposal.targets + ) + ), + keccak256( + AbiCoder.prototype.encode( + proposal.values.map(() => 'uint256'), + proposal.values + ) + ), + keccak256( + AbiCoder.prototype.encode( + proposal.calldatas.map(() => 'bytes32'), + proposal.calldatas.map((calldata) => keccak256(calldata)) + ) + ), + keccak256( + AbiCoder.prototype.encode( + proposal.gasAmounts.map(() => 'uint256'), + proposal.gasAmounts + ) + ), + ]) + ); + +export const getGlobalProposalHash = (proposal: GlobalProposalDetailStruct) => + keccak256( + AbiCoder.prototype.encode(globalProposalParamTypes, [ + globalProposalTypeHash, + proposal.nonce, + keccak256( + AbiCoder.prototype.encode( + proposal.targetOptions.map(() => 'uint8'), + proposal.targetOptions + ) + ), + keccak256( + AbiCoder.prototype.encode( + proposal.values.map(() => 'uint256'), + proposal.values + ) + ), + keccak256( + AbiCoder.prototype.encode( + proposal.calldatas.map(() => 'bytes32'), + proposal.calldatas.map((calldata) => keccak256(calldata)) + ) + ), + keccak256( + AbiCoder.prototype.encode( + proposal.gasAmounts.map(() => 'uint256'), + proposal.gasAmounts + ) + ), + ]) + ); + +export const getBallotHash = (proposalHash: string, support: BigNumberish) => + keccak256(AbiCoder.prototype.encode(ballotParamTypes, [ballotTypeHash, proposalHash, support])); + +export const getBallotDigest = (domainSeparator: string, proposalHash: string, support: BigNumberish): string => + solidityKeccak256( + ['bytes1', 'bytes1', 'bytes32', 'bytes32'], + ['0x19', '0x01', domainSeparator, getBallotHash(proposalHash, support)] + ); + +export interface BOsBallot { + period: BigNumberish; + operators: WeightedAddressStruct[]; +} + +export const getBOsBallotHash = (period: BigNumberish, operators: WeightedAddressStruct[]) => + keccak256( + AbiCoder.prototype.encode(bridgeOperatorsBallotParamTypes, [ + bridgeOperatorsBallotTypeHash, + period, + keccak256( + AbiCoder.prototype.encode( + operators.map(() => 'bytes32'), + operators.map(({ addr, weight }) => + keccak256(AbiCoder.prototype.encode(bridgeOperatorParamTypes, [bridgeOperatorTypeHash, addr, weight])) + ) + ) + ), + ]) + ); + +export const getBOsBallotDigest = ( + domainSeparator: string, + period: BigNumberish, + operators: WeightedAddressStruct[] +): string => + solidityKeccak256( + ['bytes1', 'bytes1', 'bytes32', 'bytes32'], + ['0x19', '0x01', domainSeparator, getBOsBallotHash(period, operators)] + ); diff --git a/test/governance-admin/GovernanceAdmin.test.ts b/test/governance-admin/GovernanceAdmin.test.ts new file mode 100644 index 000000000..7cdedeb35 --- /dev/null +++ b/test/governance-admin/GovernanceAdmin.test.ts @@ -0,0 +1,145 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { expect } from 'chai'; +import { BigNumber } from 'ethers'; +import { ethers } from 'hardhat'; + +import { GovernanceAdminInterface, mapByteSigToSigStruct } from '../../src/script/governance-admin-interface'; +import { BOsBallot, BridgeOperatorsBallotTypes, VoteType } from '../../src/script/proposal'; +import { + IBridge, + MainchainGovernanceAdmin, + MainchainGovernanceAdmin__factory, + RoninGovernanceAdmin, + RoninGovernanceAdmin__factory, + Staking, + Staking__factory, +} from '../../src/types'; +import { MockBridge__factory } from '../../src/types/factories/MockBridge__factory'; +import { ProposalDetailStruct } from '../../src/types/GovernanceAdmin'; +import { SignatureStruct } from '../../src/types/RoninGovernanceAdmin'; +import { initTest } from '../helpers/fixture'; + +let deployer: SignerWithAddress; +let relayer: SignerWithAddress; +let governors: SignerWithAddress[]; + +let bridgeContract: IBridge; +let stakingContract: Staking; +let mainchainGovernanceAdmin: MainchainGovernanceAdmin; +let governanceAdmin: RoninGovernanceAdmin; +let governanceAdminInterface: GovernanceAdminInterface; + +let proposal: ProposalDetailStruct; +let supports: VoteType[]; +let signatures: SignatureStruct[]; +let ballot: BOsBallot; + +describe('Governance Admin test', () => { + before(async () => { + [deployer, relayer, ...governors] = await ethers.getSigners(); + governors = governors.slice(0, 10); + governors = governors.sort((v1, v2) => v1.address.toLowerCase().localeCompare(v2.address.toLowerCase())); + + bridgeContract = await new MockBridge__factory(deployer).deploy(); + + const { roninGovernanceAdminAddress, mainchainGovernanceAdminAddress, stakingContractAddress } = await initTest( + 'RoninGovernanceAdmin.test' + )({ + trustedOrganizations: governors.map((v) => ({ addr: v.address, weight: 100 })), + numerator: 1, + denominator: 2, + relayers: [relayer.address], + bridgeContract: bridgeContract.address, + }); + + stakingContract = Staking__factory.connect(stakingContractAddress, deployer); + governanceAdmin = RoninGovernanceAdmin__factory.connect(roninGovernanceAdminAddress, deployer); + governanceAdminInterface = new GovernanceAdminInterface(governanceAdmin, ...governors); + mainchainGovernanceAdmin = MainchainGovernanceAdmin__factory.connect(mainchainGovernanceAdminAddress, deployer); + }); + + it('Should be able to propose to change staking config', async () => { + proposal = await governanceAdminInterface.createProposal( + stakingContract.address, + 0, + governanceAdminInterface.interface.encodeFunctionData('functionDelegateCall', [ + stakingContract.interface.encodeFunctionData('setMinValidatorBalance', [555]), + ]), + 500_000 + ); + signatures = await governanceAdminInterface.generateSignatures(proposal); + supports = signatures.map(() => VoteType.For); + + await governanceAdmin.connect(governors[0]).proposeProposalStructAndCastVotes(proposal, supports, signatures); + expect(await stakingContract.minValidatorBalance()).eq(555); + }); + + it('Should not be able to reuse already voted signatures or proposals', async () => { + await expect( + governanceAdmin.connect(governors[0]).proposeProposalStructAndCastVotes(proposal, supports, signatures) + ).revertedWith('CoreGovernance: invalid proposal nonce'); + }); + + it('Should be able to relay to mainchain governance admin contract', async () => { + await mainchainGovernanceAdmin.connect(relayer).relayProposal(proposal, supports, signatures); + }); + + it('Should not be able to relay again', async () => { + await expect(mainchainGovernanceAdmin.connect(relayer).relayProposal(proposal, supports, signatures)).revertedWith( + 'CoreGovernance: invalid proposal nonce' + ); + }); + + it('Should be able to vote bridge operators', async () => { + ballot = { + period: 10, + operators: governors.map((v) => ({ addr: v.address, weight: 100 })), + }; + signatures = await Promise.all( + governors.map((g) => + g + ._signTypedData(governanceAdminInterface.domain, BridgeOperatorsBallotTypes, ballot) + .then(mapByteSigToSigStruct) + ) + ); + await governanceAdmin.voteBridgeOperatorsBySignatures(ballot.period, ballot.operators, signatures); + expect(await bridgeContract.getBridgeOperators()).eql(governors.map((v) => [v.address, BigNumber.from(100)])); + }); + + it('Should be able relay vote bridge operators', async () => { + await mainchainGovernanceAdmin.connect(relayer).relayBridgeOperators(ballot.period, ballot.operators, signatures); + }); + + it('Should not able to relay again', async () => { + await expect( + mainchainGovernanceAdmin.connect(relayer).relayBridgeOperators(ballot.period, ballot.operators, signatures) + ).revertedWith('BOsGovernanceRelay: query for outdated period'); + }); + + it('Should not be able to use the signatures for another period', async () => { + ballot = { + period: 100, + operators: governors.map((v) => ({ addr: v.address, weight: 100 })), + }; + await expect( + governanceAdmin.voteBridgeOperatorsBySignatures(ballot.period, ballot.operators, signatures) + ).revertedWith('BOsGovernanceProposal: invalid order'); + }); + + it('Should not be able to vote bridge operators with a smaller period', async () => { + ballot = { + period: 5, + operators: governors.map((v) => ({ addr: v.address, weight: 100 })), + }; + signatures = await Promise.all( + governors.map((g) => + g + ._signTypedData(governanceAdminInterface.domain, BridgeOperatorsBallotTypes, ballot) + .then(mapByteSigToSigStruct) + ) + ); + await expect( + governanceAdmin.voteBridgeOperatorsBySignatures(ballot.period, ballot.operators, signatures) + ).revertedWith('BOsGovernanceProposal: query for outdated period'); + }); +}); diff --git a/test/helpers/fixture.ts b/test/helpers/fixture.ts index fcc8ce9b0..9c6243f0a 100644 --- a/test/helpers/fixture.ts +++ b/test/helpers/fixture.ts @@ -1,12 +1,14 @@ -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { BigNumber, BytesLike } from 'ethers'; +import { BigNumber } from 'ethers'; import { deployments, ethers, network } from 'hardhat'; import { Address } from 'hardhat-deploy/dist/types'; import { - initAddress, + MainchainGovernanceAdminArguments, + mainchainGovernanceAdminConf, MaintenanceArguments, maintenanceConf, Network, + RoninGovernanceAdminArguments, + roninGovernanceAdminConf, RoninTrustedOrganizationArguments, roninTrustedOrganizationConf, RoninValidatorSetArguments, @@ -18,11 +20,13 @@ import { StakingVestingArguments, stakingVestingConfig, } from '../../src/config'; -import { TransparentUpgradeableProxyV2__factory } from '../../src/types'; export interface InitTestOutput { + roninGovernanceAdminAddress: Address; + mainchainGovernanceAdminAddress: Address; maintenanceContractAddress: Address; roninTrustedOrganizationAddress: Address; + mainchainRoninTrustedOrganizationAddress: Address; slashContractAddress: Address; stakingContractAddress: Address; stakingVestingContractAddress: Address; @@ -53,6 +57,12 @@ export const defaultTestConfig = { maxSchedules: 2, trustedOrganizations: [], + numerator: 0, + denominator: 1, + + roleSetter: ethers.constants.AddressZero, + bridgeContract: ethers.constants.AddressZero, + relayers: [], }; export const initTest = (id: string) => @@ -63,10 +73,11 @@ export const initTest = (id: string) => StakingVestingArguments & SlashIndicatorArguments & RoninValidatorSetArguments & - RoninTrustedOrganizationArguments & { governanceAdmin: Address } + RoninTrustedOrganizationArguments & + RoninGovernanceAdminArguments & + MainchainGovernanceAdminArguments >(async ({ deployments }, options) => { if (network.name == Network.Hardhat) { - initAddress[network.name] = { governanceAdmin: options?.governanceAdmin ?? ethers.constants.AddressZero }; maintenanceConf[network.name] = { minMaintenanceBlockPeriod: options?.minMaintenanceBlockPeriod ?? defaultTestConfig.minMaintenanceBlockPeriod, maxMaintenanceBlockPeriod: options?.maxMaintenanceBlockPeriod ?? defaultTestConfig.maxMaintenanceBlockPeriod, @@ -99,49 +110,51 @@ export const initTest = (id: string) => }; roninTrustedOrganizationConf[network.name] = { trustedOrganizations: options?.trustedOrganizations ?? defaultTestConfig.trustedOrganizations, + numerator: options?.numerator ?? defaultTestConfig.numerator, + denominator: options?.denominator ?? defaultTestConfig.denominator, + }; + roninGovernanceAdminConf[network.name] = { + bridgeContract: options?.bridgeContract ?? defaultTestConfig.bridgeContract, + }; + mainchainGovernanceAdminConf[network.name] = { + roleSetter: options?.roleSetter ?? defaultTestConfig.roleSetter, + bridgeContract: options?.bridgeContract ?? defaultTestConfig.bridgeContract, + relayers: options?.relayers ?? defaultTestConfig.relayers, }; } await deployments.fixture([ 'CalculateAddresses', + 'RoninGovernanceAdmin', 'RoninValidatorSetProxy', 'SlashIndicatorProxy', 'StakingProxy', 'MaintenanceProxy', 'StakingVestingProxy', + 'MainchainGovernanceAdmin', + 'MainchainRoninTrustedOrganizationProxy', id, ]); + const roninGovernanceAdminDeployment = await deployments.get('RoninGovernanceAdmin'); + const mainchainGovernanceAdminDeployment = await deployments.get('MainchainGovernanceAdmin'); const maintenanceContractDeployment = await deployments.get('MaintenanceProxy'); const roninTrustedOrganizationDeployment = await deployments.get('RoninTrustedOrganizationProxy'); + const mainchainRoninTrustedOrganizationDeployment = await deployments.get('MainchainRoninTrustedOrganizationProxy'); const slashContractDeployment = await deployments.get('SlashIndicatorProxy'); const stakingContractDeployment = await deployments.get('StakingProxy'); const stakingVestingContractDeployment = await deployments.get('StakingVestingProxy'); const validatorContractDeployment = await deployments.get('RoninValidatorSetProxy'); return { + roninGovernanceAdminAddress: roninGovernanceAdminDeployment.address, + mainchainGovernanceAdminAddress: mainchainGovernanceAdminDeployment.address, maintenanceContractAddress: maintenanceContractDeployment.address, roninTrustedOrganizationAddress: roninTrustedOrganizationDeployment.address, + mainchainRoninTrustedOrganizationAddress: mainchainRoninTrustedOrganizationDeployment.address, slashContractAddress: slashContractDeployment.address, stakingContractAddress: stakingContractDeployment.address, stakingVestingContractAddress: stakingVestingContractDeployment.address, validatorContractAddress: validatorContractDeployment.address, }; }, id); - -export class GovernanceAdminInterface { - signer!: SignerWithAddress; - address = ethers.constants.AddressZero; - constructor(signer: SignerWithAddress) { - this.signer = signer; - this.address = signer.address; - } - - functionDelegateCall(to: Address, data: BytesLike) { - return TransparentUpgradeableProxyV2__factory.connect(to, this.signer).functionDelegateCall(data); - } - - upgrade(from: Address, to: Address) { - return TransparentUpgradeableProxyV2__factory.connect(from, this.signer).upgradeTo(to); - } -} diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index c0ac9c6ca..3ecf381ed 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -11,17 +11,21 @@ import { Staking__factory, MockRoninValidatorSetExtended__factory, MockRoninValidatorSetExtended, + RoninGovernanceAdmin, + RoninGovernanceAdmin__factory, } from '../../src/types'; import { expects as RoninValidatorSetExpects } from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../helpers/utils'; import { SlashType } from '../../src/script/slash-indicator'; -import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; +import { initTest } from '../helpers/fixture'; +import { GovernanceAdminInterface } from '../../src/script/governance-admin-interface'; let slashContract: SlashIndicator; let stakingContract: Staking; let validatorContract: MockRoninValidatorSetExtended; -let governanceAdmin: GovernanceAdminInterface; +let governanceAdmin: RoninGovernanceAdmin; +let governanceAdminInterface: GovernanceAdminInterface; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; @@ -41,27 +45,28 @@ describe('[Integration] Slash validators', () => { before(async () => { [deployer, coinbase, governor, ...validatorCandidates] = await ethers.getSigners(); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); - governanceAdmin = new GovernanceAdminInterface(governor); - - const { slashContractAddress, stakingContractAddress, validatorContractAddress } = await initTest( - 'ActionSlashValidators' - )({ - felonyJailBlocks, - misdemeanorThreshold, - felonyThreshold, - slashFelonyAmount, - slashDoubleSignAmount, - minValidatorBalance, - governanceAdmin: governanceAdmin.address, - }); + + const { slashContractAddress, stakingContractAddress, validatorContractAddress, roninGovernanceAdminAddress } = + await initTest('ActionSlashValidators')({ + felonyJailBlocks, + misdemeanorThreshold, + felonyThreshold, + slashFelonyAmount, + slashDoubleSignAmount, + minValidatorBalance, + trustedOrganizations: [{ addr: governor.address, weight: 100 }], + }); slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); validatorContract = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); + governanceAdmin = RoninGovernanceAdmin__factory.connect(roninGovernanceAdminAddress, deployer); + governanceAdminInterface = new GovernanceAdminInterface(governanceAdmin, governor); const mockValidatorLogic = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); await mockValidatorLogic.deployed(); - await governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); + await governanceAdminInterface.upgrade(validatorContract.address, mockValidatorLogic.address); + await network.provider.send('hardhat_mine', [ ethers.utils.hexStripZeros(BigNumber.from(numberOfBlocksInEpoch * numberOfEpochsInPeriod).toHexString()), ]); diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index 288ba7723..182685146 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -11,14 +11,18 @@ import { Staking__factory, MockRoninValidatorSetExtended__factory, MockRoninValidatorSetExtended, + RoninGovernanceAdmin, + RoninGovernanceAdmin__factory, } from '../../src/types'; import { mineBatchTxs } from '../helpers/utils'; -import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; +import { initTest } from '../helpers/fixture'; +import { GovernanceAdminInterface } from '../../src/script/governance-admin-interface'; let slashContract: SlashIndicator; let stakingContract: Staking; let validatorContract: MockRoninValidatorSetExtended; -let governanceAdmin: GovernanceAdminInterface; +let governanceAdmin: RoninGovernanceAdmin; +let governanceAdminInterface: GovernanceAdminInterface; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; @@ -37,26 +41,26 @@ describe('[Integration] Submit Block Reward', () => { before(async () => { [deployer, coinbase, governor, ...validatorCandidates] = await ethers.getSigners(); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); - governanceAdmin = new GovernanceAdminInterface(governor); - - const { slashContractAddress, stakingContractAddress, validatorContractAddress } = await initTest( - 'ActionSubmitReward' - )({ - felonyThreshold, - minValidatorBalance, - bonusPerBlock, - slashFelonyAmount, - slashDoubleSignAmount, - governanceAdmin: governanceAdmin.address, - }); + + const { slashContractAddress, stakingContractAddress, validatorContractAddress, roninGovernanceAdminAddress } = + await initTest('ActionSubmitReward')({ + felonyThreshold, + minValidatorBalance, + bonusPerBlock, + slashFelonyAmount, + slashDoubleSignAmount, + trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: 100 })), + }); slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); validatorContract = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); + governanceAdmin = RoninGovernanceAdmin__factory.connect(roninGovernanceAdminAddress, deployer); + governanceAdminInterface = new GovernanceAdminInterface(governanceAdmin, governor); const mockValidatorLogic = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); await mockValidatorLogic.deployed(); - await governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); + await governanceAdminInterface.upgrade(validatorContract.address, mockValidatorLogic.address); }); describe('Configuration check', async () => { diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index 73111e46b..cd6f08842 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -10,16 +10,20 @@ import { Staking__factory, MockRoninValidatorSetExtended__factory, MockRoninValidatorSetExtended, + RoninGovernanceAdmin, + RoninGovernanceAdmin__factory, } from '../../src/types'; import { expects as StakingExpects } from '../helpers/reward-calculation'; import { expects as ValidatorSetExpects } from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../helpers/utils'; -import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; +import { initTest } from '../helpers/fixture'; +import { GovernanceAdminInterface } from '../../src/script/governance-admin-interface'; let slashContract: SlashIndicator; let stakingContract: Staking; let validatorContract: MockRoninValidatorSetExtended; -let governanceAdmin: GovernanceAdminInterface; +let governanceAdmin: RoninGovernanceAdmin; +let governanceAdminInterface: GovernanceAdminInterface; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; @@ -38,25 +42,25 @@ describe('[Integration] Wrap up epoch', () => { before(async () => { [deployer, coinbase, governor, ...validatorCandidates] = await ethers.getSigners(); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); - governanceAdmin = new GovernanceAdminInterface(governor); - - const { slashContractAddress, stakingContractAddress, validatorContractAddress } = await initTest( - 'ActionWrapUpEpoch' - )({ - felonyThreshold, - slashFelonyAmount, - slashDoubleSignAmount, - minValidatorBalance, - maxValidatorNumber, - governanceAdmin: governanceAdmin.address, - }); + + const { slashContractAddress, stakingContractAddress, validatorContractAddress, roninGovernanceAdminAddress } = + await initTest('ActionWrapUpEpoch')({ + felonyThreshold, + slashFelonyAmount, + slashDoubleSignAmount, + minValidatorBalance, + maxValidatorNumber, + trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: 100 })), + }); slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); validatorContract = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); + governanceAdmin = RoninGovernanceAdmin__factory.connect(roninGovernanceAdminAddress, deployer); + governanceAdminInterface = new GovernanceAdminInterface(governanceAdmin, governor); const mockValidatorLogic = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); await mockValidatorLogic.deployed(); - await governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); + await governanceAdminInterface.upgrade(validatorContract.address, mockValidatorLogic.address); }); after(async () => { diff --git a/test/integration/Configuration.test.ts b/test/integration/Configuration.test.ts index 6084e06fb..c1cfe34ac 100644 --- a/test/integration/Configuration.test.ts +++ b/test/integration/Configuration.test.ts @@ -25,7 +25,7 @@ let validatorContract: RoninValidatorSet; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; -let governanceAdmin: SignerWithAddress; +let governor: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; const felonyJailBlocks = 28800 * 2; @@ -51,7 +51,7 @@ const maxSchedules = 2; describe('[Integration] Configuration check', () => { before(async () => { - [coinbase, deployer, governanceAdmin, ...validatorCandidates] = await ethers.getSigners(); + [coinbase, deployer, governor, ...validatorCandidates] = await ethers.getSigners(); const { maintenanceContractAddress, slashContractAddress, @@ -76,7 +76,7 @@ describe('[Integration] Configuration check', () => { maxMaintenanceBlockPeriod, minOffset, maxSchedules, - governanceAdmin: governanceAdmin.address, + trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: 100 })), }); stakingVestingContract = StakingVesting__factory.connect(stakingVestingContractAddress, deployer); diff --git a/test/maintainance/Maintenance.test.ts b/test/maintainance/Maintenance.test.ts index 7ea12448a..c9c5df1ff 100644 --- a/test/maintainance/Maintenance.test.ts +++ b/test/maintainance/Maintenance.test.ts @@ -5,7 +5,6 @@ import { BigNumber, BigNumberish } from 'ethers'; import { RoninValidatorSet, - RoninValidatorSet__factory, Maintenance, Maintenance__factory, SlashIndicator, @@ -13,9 +12,12 @@ import { Staking, Staking__factory, MockRoninValidatorSetSorting__factory, + RoninGovernanceAdmin, + RoninGovernanceAdmin__factory, } from '../../src/types'; -import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; +import { initTest } from '../helpers/fixture'; import { EpochController } from '../helpers/ronin-validator-set'; +import { GovernanceAdminInterface } from '../../src/script/governance-admin-interface'; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; @@ -26,7 +28,8 @@ let maintenanceContract: Maintenance; let slashContract: SlashIndicator; let stakingContract: Staking; let validatorContract: RoninValidatorSet; -let governanceAdmin: GovernanceAdminInterface; +let governanceAdmin: RoninGovernanceAdmin; +let governanceAdminInterface: GovernanceAdminInterface; let localEpochController: EpochController; @@ -47,21 +50,27 @@ let currentBlock: number; describe('Maintenance test', () => { before(async () => { [deployer, coinbase, governor, ...validatorCandidates] = await ethers.getSigners(); - governanceAdmin = new GovernanceAdminInterface(governor); - const { maintenanceContractAddress, slashContractAddress, stakingContractAddress, validatorContractAddress } = - await initTest('Maintenance')({ - governanceAdmin: governor.address, - misdemeanorThreshold, - felonyThreshold, - }); + const { + maintenanceContractAddress, + slashContractAddress, + stakingContractAddress, + validatorContractAddress, + roninGovernanceAdminAddress, + } = await initTest('Maintenance')({ + trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: 100 })), + misdemeanorThreshold, + felonyThreshold, + }); maintenanceContract = Maintenance__factory.connect(maintenanceContractAddress, deployer); slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); validatorContract = MockRoninValidatorSetSorting__factory.connect(validatorContractAddress, deployer); + governanceAdmin = RoninGovernanceAdmin__factory.connect(roninGovernanceAdminAddress, deployer); + governanceAdminInterface = new GovernanceAdminInterface(governanceAdmin, governor); const mockValidatorLogic = await new MockRoninValidatorSetSorting__factory(deployer).deploy(); await mockValidatorLogic.deployed(); - await governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); + await governanceAdminInterface.upgrade(validatorContract.address, mockValidatorLogic.address); validatorCandidates = validatorCandidates.slice(0, maxValidatorNumber); for (let i = 0; i < maxValidatorNumber; i++) { diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index b700d6f16..b9879ee4e 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -7,18 +7,22 @@ import { MockRoninValidatorSetSorting__factory, MockSlashIndicatorExtended, MockSlashIndicatorExtended__factory, + RoninGovernanceAdmin, + RoninGovernanceAdmin__factory, RoninValidatorSet, Staking, Staking__factory, } from '../../src/types'; import { SlashType } from '../../src/script/slash-indicator'; -import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; +import { initTest } from '../helpers/fixture'; import { EpochController } from '../helpers/ronin-validator-set'; +import { GovernanceAdminInterface } from '../../src/script/governance-admin-interface'; let slashContract: MockSlashIndicatorExtended; let mockSlashLogic: MockSlashIndicatorExtended; let stakingContract: Staking; -let governanceAdmin: GovernanceAdminInterface; +let governanceAdmin: RoninGovernanceAdmin; +let governanceAdminInterface: GovernanceAdminInterface; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; @@ -66,11 +70,10 @@ const validateIndicatorAt = async (idx: number) => { describe('Slash indicator test', () => { before(async () => { [deployer, coinbase, governor, vagabond, ...validatorCandidates] = await ethers.getSigners(); - governanceAdmin = new GovernanceAdminInterface(governor); - const { slashContractAddress, stakingContractAddress, validatorContractAddress } = await initTest('SlashIndicator')( - { - governanceAdmin: governor.address, + const { slashContractAddress, stakingContractAddress, validatorContractAddress, roninGovernanceAdminAddress } = + await initTest('SlashIndicator')({ + trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: 100 })), misdemeanorThreshold, felonyThreshold, maxValidatorNumber, @@ -80,20 +83,21 @@ describe('Slash indicator test', () => { minValidatorBalance, slashFelonyAmount, slashDoubleSignAmount, - } - ); + }); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); validatorContract = MockRoninValidatorSetSorting__factory.connect(validatorContractAddress, deployer); slashContract = MockSlashIndicatorExtended__factory.connect(slashContractAddress, deployer); + governanceAdmin = RoninGovernanceAdmin__factory.connect(roninGovernanceAdminAddress, deployer); + governanceAdminInterface = new GovernanceAdminInterface(governanceAdmin, governor); const mockValidatorLogic = await new MockRoninValidatorSetSorting__factory(deployer).deploy(); await mockValidatorLogic.deployed(); - await governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); + await governanceAdminInterface.upgrade(validatorContract.address, mockValidatorLogic.address); mockSlashLogic = await new MockSlashIndicatorExtended__factory(deployer).deploy(); await mockSlashLogic.deployed(); - await governanceAdmin.upgrade(slashContractAddress, mockSlashLogic.address); + await governanceAdminInterface.upgrade(slashContractAddress, mockSlashLogic.address); validatorCandidates = validatorCandidates.slice(0, maxValidatorNumber); for (let i = 0; i < maxValidatorNumber; i++) { diff --git a/test/validator/ArrangeValidators.test.ts b/test/validator/ArrangeValidators.test.ts index b8edd3f44..174c5f5ff 100644 --- a/test/validator/ArrangeValidators.test.ts +++ b/test/validator/ArrangeValidators.test.ts @@ -11,13 +11,17 @@ import { MockSlashIndicatorExtended, RoninTrustedOrganization__factory, RoninTrustedOrganization, + RoninGovernanceAdmin, + RoninGovernanceAdmin__factory, } from '../../src/types'; -import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; +import { initTest } from '../helpers/fixture'; +import { GovernanceAdminInterface } from '../../src/script/governance-admin-interface'; let validatorContract: MockRoninValidatorSetExtended; let slashIndicator: MockSlashIndicatorExtended; -let governanceAdmin: GovernanceAdminInterface; let roninTrustedOrganization: RoninTrustedOrganization; +let governanceAdmin: RoninGovernanceAdmin; +let governanceAdminInterface: GovernanceAdminInterface; let deployer: SignerWithAddress; let governor: SignerWithAddress; @@ -32,11 +36,13 @@ const setPriorityStatus = async (addrs: Address[], statuses: boolean[]) => { const arr = statuses.map((stt, i) => ({ address: addrs[i], stt })); const addingTrustedOrgs = arr.filter(({ stt }) => stt).map(({ address }) => address); const removingTrustedOrgs = arr.filter(({ stt }) => !stt).map(({ address }) => address); - await governanceAdmin.functionDelegateCall( + await governanceAdminInterface.functionDelegateCall( roninTrustedOrganization.address, - roninTrustedOrganization.interface.encodeFunctionData('addTrustedOrganizations', [addingTrustedOrgs]) + roninTrustedOrganization.interface.encodeFunctionData('addTrustedOrganizations', [ + addingTrustedOrgs.map((v) => ({ addr: v, weight: 100 })), + ]) ); - await governanceAdmin.functionDelegateCall( + await governanceAdminInterface.functionDelegateCall( roninTrustedOrganization.address, roninTrustedOrganization.interface.encodeFunctionData('removeTrustedOrganizations', [removingTrustedOrgs]) ); @@ -70,29 +76,33 @@ const sortArrayByBoolean = (indexes: number[], statuses: boolean[]) => { describe('Arrange validators', () => { before(async () => { [deployer, governor, ...validatorCandidates] = await ethers.getSigners(); - governanceAdmin = new GovernanceAdminInterface(governor); - const { slashContractAddress, validatorContractAddress, roninTrustedOrganizationAddress } = await initTest( - 'ArrangeValidators' - )({ - governanceAdmin: governor.address, + const { + slashContractAddress, + validatorContractAddress, + roninTrustedOrganizationAddress, + roninGovernanceAdminAddress, + } = await initTest('ArrangeValidators')({ maxValidatorNumber, maxValidatorCandidate, maxPrioritizedValidatorNumber, slashFelonyAmount, + trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: 100 })), }); validatorContract = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); slashIndicator = MockSlashIndicatorExtended__factory.connect(slashContractAddress, deployer); roninTrustedOrganization = RoninTrustedOrganization__factory.connect(roninTrustedOrganizationAddress, deployer); + governanceAdmin = RoninGovernanceAdmin__factory.connect(roninGovernanceAdminAddress, deployer); + governanceAdminInterface = new GovernanceAdminInterface(governanceAdmin, governor); const mockValidatorLogic = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); await mockValidatorLogic.deployed(); - await governanceAdmin.upgrade(validatorContract.address, mockValidatorLogic.address); + await governanceAdminInterface.upgrade(validatorContract.address, mockValidatorLogic.address); const mockSlashIndicator = await new MockSlashIndicatorExtended__factory(deployer).deploy(); await mockSlashIndicator.deployed(); - await governanceAdmin.upgrade(slashIndicator.address, mockSlashIndicator.address); + await governanceAdminInterface.upgrade(slashIndicator.address, mockSlashIndicator.address); }); describe('Update priority list', async () => { @@ -171,6 +181,7 @@ describe('Arrange validators', () => { inputValidatorAddrs, actualPrioritizedNumber + actualRegularNumber ); + expect(outputValidators).eql(expectingValidatorAddrs); }); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index feff611fe..ea52ee589 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -10,15 +10,19 @@ import { Staking__factory, MockSlashIndicatorExtended__factory, MockSlashIndicatorExtended, + RoninGovernanceAdmin__factory, + RoninGovernanceAdmin, } from '../../src/types'; import * as RoninValidatorSet from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../helpers/utils'; -import { GovernanceAdminInterface, initTest } from '../helpers/fixture'; +import { initTest } from '../helpers/fixture'; +import { GovernanceAdminInterface } from '../../src/script/governance-admin-interface'; let roninValidatorSet: MockRoninValidatorSetExtended; let stakingContract: Staking; let slashIndicator: MockSlashIndicatorExtended; -let governanceAdmin: GovernanceAdminInterface; +let governanceAdmin: RoninGovernanceAdmin; +let governanceAdminInterface: GovernanceAdminInterface; let coinbase: SignerWithAddress; let treasury: SignerWithAddress; @@ -36,32 +40,32 @@ const bonusPerBlock = BigNumber.from(1); describe('Ronin Validator Set test', () => { before(async () => { [coinbase, treasury, deployer, governor, ...validatorCandidates] = await ethers.getSigners(); - governanceAdmin = new GovernanceAdminInterface(governor); validatorCandidates = validatorCandidates.slice(0, 5); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); - const { slashContractAddress, validatorContractAddress, stakingContractAddress } = await initTest( - 'RoninValidatorSet' - )({ - governanceAdmin: governor.address, - minValidatorBalance, - maxValidatorNumber, - maxValidatorCandidate, - slashFelonyAmount, - bonusPerBlock, - }); + const { slashContractAddress, validatorContractAddress, stakingContractAddress, roninGovernanceAdminAddress } = + await initTest('RoninValidatorSet')({ + trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: 100 })), + minValidatorBalance, + maxValidatorNumber, + maxValidatorCandidate, + slashFelonyAmount, + bonusPerBlock, + }); roninValidatorSet = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); slashIndicator = MockSlashIndicatorExtended__factory.connect(slashContractAddress, deployer); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); + governanceAdmin = RoninGovernanceAdmin__factory.connect(roninGovernanceAdminAddress, deployer); + governanceAdminInterface = new GovernanceAdminInterface(governanceAdmin, governor); const mockValidatorLogic = await new MockRoninValidatorSetExtended__factory(deployer).deploy(); await mockValidatorLogic.deployed(); - await governanceAdmin.upgrade(roninValidatorSet.address, mockValidatorLogic.address); + await governanceAdminInterface.upgrade(roninValidatorSet.address, mockValidatorLogic.address); const mockSlashIndicator = await new MockSlashIndicatorExtended__factory(deployer).deploy(); await mockSlashIndicator.deployed(); - await governanceAdmin.upgrade(slashIndicator.address, mockSlashIndicator.address); + await governanceAdminInterface.upgrade(slashIndicator.address, mockSlashIndicator.address); }); after(async () => { From 871d85aeea9a60101f005b10504c41dc2212cb2d Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Fri, 21 Oct 2022 10:13:46 +0700 Subject: [PATCH 173/190] [CandidateManager] Expose function to get candidate info (#31) Expose functions --- contracts/interfaces/ICandidateManager.sol | 7 ++++++- contracts/ronin/validator/CandidateManager.sol | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/contracts/interfaces/ICandidateManager.sol b/contracts/interfaces/ICandidateManager.sol index 5dbda6559..77bf15e7e 100644 --- a/contracts/interfaces/ICandidateManager.sol +++ b/contracts/interfaces/ICandidateManager.sol @@ -82,10 +82,15 @@ interface ICandidateManager { function getValidatorCandidates() external view returns (address[] memory); /** - * @dev Returns candidates info. + * @dev Returns all candidate info. */ function getCandidateInfos() external view returns (ValidatorCandidate[] memory); + /** + * @dev Returns the info of a candidate. + */ + function getCandidateInfo(address _candidate) external view returns (ValidatorCandidate memory); + /** * @dev Returns whether the address is the candidate admin. */ diff --git a/contracts/ronin/validator/CandidateManager.sol b/contracts/ronin/validator/CandidateManager.sol index 8fa7cd706..8d7a59e8b 100644 --- a/contracts/ronin/validator/CandidateManager.sol +++ b/contracts/ronin/validator/CandidateManager.sol @@ -87,6 +87,14 @@ abstract contract CandidateManager is ICandidateManager, HasStakingContract { } } + /** + * @inheritdoc ICandidateManager + */ + function getCandidateInfo(address _candidate) external view override returns (ValidatorCandidate memory) { + require(isValidatorCandidate(_candidate), "CandidateManager: query for non-existent candidate"); + return _candidateInfo[_candidate]; + } + /** * @inheritdoc ICandidateManager */ From 2d51d29a9464bc4553527b0c39ce1b19d080c4d6 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Mon, 24 Oct 2022 10:18:28 +0700 Subject: [PATCH 174/190] [RewardCalculation] Store period number instead of block number (#33) [RewardCalculation] store period number instead of block number --- contracts/interfaces/IRewardPool.sol | 8 ++++---- contracts/ronin/staking/RewardCalculation.sol | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/contracts/interfaces/IRewardPool.sol b/contracts/interfaces/IRewardPool.sol index 9ced24224..d0196601c 100644 --- a/contracts/interfaces/IRewardPool.sol +++ b/contracts/interfaces/IRewardPool.sol @@ -19,8 +19,8 @@ interface IRewardPool { uint256 debited; // The amount rewards that user have already earned. uint256 credited; - // Last block number that the info updated. - uint256 lastSyncedBlock; + // Last period number that the info updated. + uint256 lastSyncedPeriod; } struct SettledRewardFields { @@ -36,8 +36,8 @@ interface IRewardPool { } struct SettledPool { - // Last block number that the info updated. - uint256 lastSyncedBlock; + // Last period number that the info updated. + uint256 lastSyncedPeriod; // Accumulated of the amount rewards per share (one unit staking). uint256 accumulatedRps; } diff --git a/contracts/ronin/staking/RewardCalculation.sol b/contracts/ronin/staking/RewardCalculation.sol index ec2ee231c..ad3dc1bdc 100644 --- a/contracts/ronin/staking/RewardCalculation.sol +++ b/contracts/ronin/staking/RewardCalculation.sol @@ -27,7 +27,7 @@ abstract contract RewardCalculation is IRewardPool { PendingPool memory _pool = _pendingPool[_poolAddr]; uint256 _balance = balanceOf(_poolAddr, _user); - if (_rewardSinked(_poolAddr, _periodOf(_reward.lastSyncedBlock))) { + if (_rewardSinked(_poolAddr, _reward.lastSyncedPeriod)) { SettledRewardFields memory _sReward = _sUserReward[_poolAddr][_user]; uint256 _diffRps = _pool.accumulatedRps - _sReward.accumulatedRps; return (_balance * _diffRps) / 1e18 + _sReward.debited; @@ -45,10 +45,10 @@ abstract contract RewardCalculation is IRewardPool { SettledPool memory _sPool = _settledPool[_poolAddr]; uint256 _diffRps = _sPool.accumulatedRps - _sReward.accumulatedRps; - if (_reward.lastSyncedBlock <= _sPool.lastSyncedBlock) { + if (_reward.lastSyncedPeriod <= _sPool.lastSyncedPeriod) { uint256 _currentBalance = balanceOf(_poolAddr, _user); - if (_rewardSinked(_poolAddr, _periodOf(_reward.lastSyncedBlock))) { + if (_rewardSinked(_poolAddr, _reward.lastSyncedPeriod)) { return (_currentBalance * _diffRps) / 1e18 + _sReward.debited; } @@ -93,7 +93,7 @@ abstract contract RewardCalculation is IRewardPool { SettledPool memory _sPool = _settledPool[_poolAddr]; // Syncs the reward once the last sync is settled. - if (_reward.lastSyncedBlock <= _sPool.lastSyncedBlock) { + if (_reward.lastSyncedPeriod <= _sPool.lastSyncedPeriod) { uint256 _claimableReward = getClaimableReward(_poolAddr, _user); SettledRewardFields storage _sReward = _sUserReward[_poolAddr][_user]; @@ -108,7 +108,7 @@ abstract contract RewardCalculation is IRewardPool { _reward.debited = _debited; _reward.credited = _credited; - _reward.lastSyncedBlock = block.number; + _reward.lastSyncedPeriod = _periodOf(block.number); emit PendingRewardUpdated(_poolAddr, _user, _debited, _credited); } @@ -129,13 +129,13 @@ abstract contract RewardCalculation is IRewardPool { SettledRewardFields storage _sReward = _sUserReward[_poolAddr][_user]; _sReward.debited = 0; - if (_reward.lastSyncedBlock <= _sPool.lastSyncedBlock) { + if (_reward.lastSyncedPeriod <= _sPool.lastSyncedPeriod) { _sReward.accumulatedRps = _sPool.accumulatedRps; } emit SettledRewardUpdated(_poolAddr, _user, 0, _sReward.accumulatedRps); _reward.credited += _amount; - _reward.lastSyncedBlock = block.number; + _reward.lastSyncedPeriod = _periodOf(block.number); emit PendingRewardUpdated(_poolAddr, _user, _reward.debited, _reward.credited); } @@ -187,7 +187,7 @@ abstract contract RewardCalculation is IRewardPool { SettledPool storage _sPool = _settledPool[_poolAddr]; if (_accumulatedRpsList[_i] != _sPool.accumulatedRps) { _sPool.accumulatedRps = _accumulatedRpsList[_i]; - _sPool.lastSyncedBlock = block.number; + _sPool.lastSyncedPeriod = _periodOf(block.number); } } emit SettledPoolsUpdated(_poolList, _accumulatedRpsList); From 3eb08238f06934480c456d5001a7dc2210540c6f Mon Sep 17 00:00:00 2001 From: Bao Date: Mon, 24 Oct 2022 16:21:08 +0700 Subject: [PATCH 175/190] Integrate with precompile of picking validator set. Feat bridge operators. (#32) * Squad commit * Add ValidatorFlag * Clean up * Fix pick new validator set contract. Add precompile. * Fix arrange validators test * Fix wrapupEpoch function - Refactor wrapUpEpoch function, separate into smaller modules - Update to new requirements. Sorting & arranging validators, then disable their ability of producing blocks regarding their in jail and maintaining status - Fix distribute delegating amount * Refactor function order * Update doc * Fix precompile and precompile-usage * Fix arrange validator precompile * Fix RoninValidatorSet test * Fix contract to deactivate block producers * Emit BridgeOperatorSetUpdated. Fix query operators function. * Fix mock contracts * Fix test * Fix EnumFlags. Fix test. * Fix (de)activate block producers function. Fix test. * Move precompile address to precompile-usage contracts * Fix test of slashing indicator * Fix test. * Fix bug in _removeCandidate function * Fix WrapUpEpoch integration test * Fix submit reward integration test * Clean up precompile contracts * Add docs for private functions * Clean up wrapping up epoch function * Add more parameters for WrappedUpEpoch event * Update docs for MiningRewardDistributed event Co-authored-by: Duc Tho Tran * Update docs for BridgeOperatorRewardDistributed event Co-authored-by: Duc Tho Tran * Minor saving gas Co-authored-by: Duc Tho Tran * Minor saving gas Co-authored-by: Duc Tho Tran * Minor fix for test of validator set * Refactor Sorting contract * Update docs for isValidator method * Add epoch number for WrappedUpEpoch event * Remove redundant casting to `IStaking` * Remove duplicated code * Fix `isValidator` callers * Fix revert message in test * Remove (De)ActivatedBlockProducers event. Add BlockProducerSetUpdated event. * Fix `_pcPickValidatorSet` result length * Fix catch precompile result length Co-authored-by: Duc Tho Tran --- contracts/interfaces/ICandidateManager.sol | 10 +- contracts/interfaces/IMaintenance.sol | 2 +- contracts/interfaces/IRoninValidatorSet.sol | 47 ++- contracts/interfaces/IStaking.sol | 1 + contracts/interfaces/IStakingVesting.sol | 62 ++- .../IHasStakingVestingContract.sol | 2 +- contracts/libraries/EnumFlags.sol | 41 ++ contracts/libraries/Sorting.sol | 104 ----- contracts/mocks/MockPrecompile.sol | 52 ++- .../mocks/MockRoninValidatorSetExtended.sol | 34 +- ...ockRoninValidatorSetOverridePrecompile.sol | 48 +++ .../mocks/MockRoninValidatorSetSorting.sol | 19 - .../mocks/MockSlashIndicatorExtended.sol | 15 +- contracts/mocks/MockValidatorSet.sol | 16 + contracts/mocks/libraries/Sorting.sol | 190 +++++++++ .../MockPrecompileUsagePickValidatorSet.sol | 37 ++ .../MockPrecompileUsageSortValidators.sol | 2 +- .../MockPrecompileUsageValidateDoubleSign.sol | 2 +- contracts/mocks/sorting/MockSorting.sol | 2 +- .../PrecompileUsagePickValidatorSet.sol | 56 +++ .../PrecompileUsageSortValidators.sol | 8 +- .../PrecompileUsageValidateDoubleSign.sol | 8 +- contracts/ronin/Maintenance.sol | 2 +- contracts/ronin/SlashIndicator.sol | 14 +- contracts/ronin/StakingVesting.sol | 98 ++++- contracts/ronin/staking/Staking.sol | 1 - contracts/ronin/staking/StakingManager.sol | 20 +- .../ronin/validator/CandidateManager.sol | 18 +- .../ronin/validator/RoninValidatorSet.sol | 386 ++++++++++++------ src/config.ts | 6 +- .../{staking-vesting.ts => bonus-reward.ts} | 0 ...vesting-proxy.ts => bonus-reward-proxy.ts} | 3 +- test/helpers/candidate-manager.ts | 44 ++ test/helpers/fixture.ts | 9 +- test/helpers/ronin-validator-set.ts | 67 ++- .../integration/ActionSlashValidators.test.ts | 236 +++++++---- test/integration/ActionSubmitReward.test.ts | 11 +- test/integration/ActionWrapUpEpoch.test.ts | 35 +- test/integration/Configuration.test.ts | 10 +- test/maintainance/Maintenance.test.ts | 44 +- test/slash/SlashIndicator.test.ts | 9 +- test/staking/Staking.test.ts | 5 +- test/validator/ArrangeValidators.test.ts | 275 +++++++------ test/validator/RoninValidatorSet.test.ts | 374 ++++++++++------- 44 files changed, 1645 insertions(+), 780 deletions(-) create mode 100644 contracts/libraries/EnumFlags.sol delete mode 100644 contracts/libraries/Sorting.sol create mode 100644 contracts/mocks/MockRoninValidatorSetOverridePrecompile.sol delete mode 100644 contracts/mocks/MockRoninValidatorSetSorting.sol create mode 100644 contracts/mocks/libraries/Sorting.sol create mode 100644 contracts/mocks/precompile-usages/MockPrecompileUsagePickValidatorSet.sol create mode 100644 contracts/precompile-usages/PrecompileUsagePickValidatorSet.sol rename src/deploy/logic/{staking-vesting.ts => bonus-reward.ts} (100%) rename src/deploy/proxy/{staking-vesting-proxy.ts => bonus-reward-proxy.ts} (91%) create mode 100644 test/helpers/candidate-manager.ts diff --git a/contracts/interfaces/ICandidateManager.sol b/contracts/interfaces/ICandidateManager.sol index 77bf15e7e..2715e7a8d 100644 --- a/contracts/interfaces/ICandidateManager.sol +++ b/contracts/interfaces/ICandidateManager.sol @@ -10,6 +10,8 @@ interface ICandidateManager { address consensusAddr; // Address that receives mining reward of the validator address payable treasuryAddr; + // Address of the bridge operator corresponding to the candidate + address bridgeOperatorAddr; // The percentile of reward that validators can be received, the rest goes to the delegators. // Values in range [0; 100_00] stands for 0-100% uint256 commissionRate; @@ -22,7 +24,12 @@ interface ICandidateManager { /// @dev Emitted when the maximum number of validator candidates is updated. event MaxValidatorCandidateUpdated(uint256 threshold); /// @dev Emitted when the validator candidate is granted. - event CandidateGranted(address indexed consensusAddr, address indexed treasuryAddr, address indexed admin); + event CandidateGranted( + address indexed consensusAddr, + address indexed treasuryAddr, + address indexed admin, + address bridgeOperator + ); /// @dev Emitted when the revoked block of a candidate is updated. event CandidateRevokedBlockUpdated(address indexed consensusAddr, uint256 revokedBlock); /// @dev Emitted when the validator candidate is revoked. @@ -57,6 +64,7 @@ interface ICandidateManager { address _admin, address _consensusAddr, address payable _treasuryAddr, + address _bridgeOperatorAddr, uint256 _commissionRate ) external; diff --git a/contracts/interfaces/IMaintenance.sol b/contracts/interfaces/IMaintenance.sol index 22198dd5f..7f67515d1 100644 --- a/contracts/interfaces/IMaintenance.sol +++ b/contracts/interfaces/IMaintenance.sol @@ -85,7 +85,7 @@ interface IMaintenance { * @dev Schedules for maintenance from `_startedAtBlock` to `_startedAtBlock`. * * Requirements: - * - The candidate `_consensusAddr` is the validator. + * - The candidate `_consensusAddr` is the block producer. * - The method caller is candidate admin of the candidate `_consensusAddr`. * - The candidate `_consensusAddr` has no schedule yet or the previous is done. * - The total number of schedules is not larger than `maxSchedules()`. diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index 3dde87fdb..abe47be26 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -15,6 +15,10 @@ interface IRoninValidatorSet is ICandidateManager { event NumberOfEpochsInPeriodUpdated(uint256); /// @dev Emitted when the validator set is updated event ValidatorSetUpdated(address[]); + /// @dev Emitted when the bridge operator set is updated, to mirror the in-jail and maintaining status of the validator. + event BlockProducerSetUpdated(address[]); + /// @dev Emitted when the bridge operator set is updated. + event BridgeOperatorSetUpdated(address[]); /// @dev Emitted when the reward of the valdiator is deprecated. event RewardDeprecated(address coinbaseAddr, uint256 rewardAmount); /// @dev Emitted when the block reward is submitted. @@ -22,9 +26,13 @@ interface IRoninValidatorSet is ICandidateManager { /// @dev Emitted when the validator is punished. event ValidatorPunished(address validatorAddr, uint256 jailedUntil, uint256 deductedStakingAmount); /// @dev Emitted when the validator reward is distributed. - event MiningRewardDistributed(address validatorAddr, uint256 amount); + event MiningRewardDistributed(address indexed validatorAddr, address indexed recipientAddr, uint256 amount); + /// @dev Emitted when the bridge operator reward is distributed. + event BridgeOperatorRewardDistributed(address indexed validatorAddr, address indexed recipientAddr, uint256 amount); /// @dev Emitted when the amount of RON reward is distributed. event StakingRewardDistributed(uint256 amount); + /// @dev Emitted when the epoch is wrapped up. + event WrappedUpEpoch(uint256 indexed periodNumber, uint256 indexed epochNumber, bool periodEnding); /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR COINBASE // @@ -52,7 +60,10 @@ interface IRoninValidatorSet is ICandidateManager { * * Emits the event `MiningRewardDistributed` when some validator has reward distributed. * Emits the event `StakingRewardDistributed` when some staking pool has reward distributed. - * Emits the event `ValidatorSetUpdated`. + * Emits the event `BlockProducerSetUpdated` when the epoch is wrapped up. + * Emits the event `BridgeOperatorSetUpdated` when the epoch is wrapped up at period ending. + * Emits the event `ValidatorSetUpdated` when the epoch is wrapped up at period ending, and the validator set gets updated. + * Emits the event `WrappedUpEpoch`. * */ function wrapUpEpoch() external payable; @@ -121,10 +132,40 @@ interface IRoninValidatorSet is ICandidateManager { function getValidators() external view returns (address[] memory); /** - * @dev Returns whether the address is validator or not. + * @dev Returns whether the address is either a bridge operator or a block producer. */ function isValidator(address _addr) external view returns (bool); + /** + * @dev Returns the current block producer list. + */ + function getBlockProducers() external view returns (address[] memory); + + /** + * @dev Returns whether the address is block producer or not. + */ + function isBlockProducer(address _addr) external view returns (bool); + + /** + * @dev Returns total numbers of the block producers. + */ + function totalBlockProducers() external view returns (uint256); + + /** + * @dev Returns the current bridge operator list. + */ + function getBridgeOperators() external view returns (address[] memory); + + /** + * @dev Returns whether the address is bridge operator or not. + */ + function isBridgeOperator(address _addr) external view returns (bool); + + /** + * @dev Returns total numbers of the bridge operators. + */ + function totalBridgeOperators() external view returns (uint256); + /** * @dev Returns whether the epoch ending is at the block number `_block`. */ diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index a98e37344..d7b1c1c66 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -139,6 +139,7 @@ interface IStaking is IRewardPool { address _candidateAdmin, address _consensusAddr, address payable _treasuryAddr, + address _bridgeOperatorAddr, uint256 _commissionRate ) external payable; diff --git a/contracts/interfaces/IStakingVesting.sol b/contracts/interfaces/IStakingVesting.sol index 449fe136f..6f6c9c879 100644 --- a/contracts/interfaces/IStakingVesting.sol +++ b/contracts/interfaces/IStakingVesting.sol @@ -3,15 +3,24 @@ pragma solidity ^0.8.9; interface IStakingVesting { - /// @dev Emitted when the block bonus is transferred. - event BlockBonusTransferred(uint256 indexed blockNumber, address indexed recipient, uint256 amount); - /// @dev Emitted when the block bonus is updated - event BonusPerBlockUpdated(uint256); + /// @dev Emitted when the block bonus for validator is transferred. + event ValidatorBonusTransferred(uint256 indexed blockNumber, address indexed recipient, uint256 amount); + /// @dev Emitted when the block bonus for bridge operator is transferred. + event BridgeOperatorBonusTransferred(uint256 indexed blockNumber, address indexed recipient, uint256 amount); + /// @dev Emitted when the block bonus for validator is updated + event ValidatorBonusPerBlockUpdated(uint256); + /// @dev Emitted when the block bonus for bridge operator is updated + event BridgeOperatorBonusPerBlockUpdated(uint256); /** - * @dev Returns the bonus amount for the block `_block`. + * @dev Returns the bonus amount for the validator at `_block`. */ - function blockBonus(uint256 _block) external view returns (uint256); + function validatorBlockBonus(uint256 _block) external view returns (uint256); + + /** + * @dev Returns the bonus amount for the bridge validator at `_block`. + */ + function bridgeOperatorBlockBonus(uint256 _block) external view returns (uint256); /** * @dev Receives RON from any address. @@ -19,20 +28,51 @@ interface IStakingVesting { function receiveRON() external payable; /** - * @dev Returns the last block number that the bonus reward is sent. + * @dev Returns the last block number that the staking vesting is sent for the validator. + */ + function lastBlockSendingValidatorBonus() external view returns (uint256); + + /** + * @dev Returns the last block number that the staking vesting is sent for the bridge operator. + */ + function lastBlockSendingBridgeOperatorBonus() external view returns (uint256); + + /** + * @dev Does two actions as in `requestValidatorBonus` and `requestBridgeOperatorBonus`. Returns + * two amounts of bonus correspondingly. + * + * Requirements: + * - The method caller is validator contract. + * - The method must be called only once per block. + * + * Emits the event `ValidatorBonusTransferred` and/or `BridgeOperatorBonusTransferred` + * + */ + function requestBonus() external returns (uint256 _validatorBonus, uint256 _bridgeOperatorBonus); + + /** + * @dev Transfers the staking vesting for the validator whenever a new block is mined. + * Returns the amount of RON sent to validator contract. + * + * Requirements: + * - The method caller is validator contract. + * - The method must be called only once per block. + * + * Emits the event `ValidatorBonusTransferred`. + * */ - function lastBonusSentBlock() external view returns (uint256); + function requestValidatorBonus() external returns (uint256); /** - * @dev Transfers the bonus reward whenever a new block is mined. + * @dev Transfers the staking vesting for the bridge operator whenever a new block is mined. * Returns the amount of RON sent to validator contract. * * Requirements: * - The method caller is validator contract. * - The method must be called only once per block. * - * Emits the event `BlockBonusTransferred`. + * Emits the event `BridgeOperatorBonusTransferred`. * */ - function requestBlockBonus() external returns (uint256); + function requestBridgeOperatorBonus() external returns (uint256); } diff --git a/contracts/interfaces/collections/IHasStakingVestingContract.sol b/contracts/interfaces/collections/IHasStakingVestingContract.sol index da3b14e2b..3fd56a781 100644 --- a/contracts/interfaces/collections/IHasStakingVestingContract.sol +++ b/contracts/interfaces/collections/IHasStakingVestingContract.sol @@ -7,7 +7,7 @@ interface IHasStakingVestingContract { event StakingVestingContractUpdated(address); /** - * @dev Returns the staking contract. + * @dev Returns the staking vesting contract. */ function stakingVestingContract() external view returns (address); diff --git a/contracts/libraries/EnumFlags.sol b/contracts/libraries/EnumFlags.sol new file mode 100644 index 000000000..ae987809f --- /dev/null +++ b/contracts/libraries/EnumFlags.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.0; + +/** + * @dev This library implements checking flag of an enumerated value. + * The originated idea is inherited from [Enum.HashFlag(Enum)](https://learn.microsoft.com/en-us/dotnet/api/system.enum.hasflag?view=net-6.0) method of C#. + */ +library EnumFlags { + enum ValidatorFlag { + None, // bit(00) + BlockProducer, // bit(01) + BridgeOperator, // bit(10) + Both // bit(11) + } + + function isNone(ValidatorFlag _value) internal pure returns (bool) { + return uint8(_value) == 0; + } + + /** + * @dev Checks if `_value` has `_flag`. + */ + function hasFlag(ValidatorFlag _value, ValidatorFlag _flag) internal pure returns (bool) { + return (uint8(_value) & uint8(_flag)) != 0; + } + + /** + * @dev Calculate new value of `_value` after adding `_flag`. + */ + function addFlag(ValidatorFlag _value, ValidatorFlag _flag) internal pure returns (ValidatorFlag) { + return ValidatorFlag(uint8(_value) | uint8(_flag)); + } + + /** + * @dev Calculate new value of `_value` after remove `_flag`. + */ + function removeFlag(ValidatorFlag _value, ValidatorFlag _flag) internal pure returns (ValidatorFlag) { + return ValidatorFlag(uint8(_value) & ~uint8(_flag)); + } +} diff --git a/contracts/libraries/Sorting.sol b/contracts/libraries/Sorting.sol deleted file mode 100644 index e9405822f..000000000 --- a/contracts/libraries/Sorting.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity ^0.8.0; - -library Sorting { - struct Node { - uint key; - uint value; - } - - function sort(uint[] memory data) internal pure returns (uint[] memory) { - return _quickSort(data, int(0), int(data.length - 1)); - } - - function sortNodes(Node[] memory nodes) internal pure returns (Node[] memory) { - // return _bubbleSortNodes(nodes); - return _quickSortNodes(nodes, int(0), int(nodes.length - 1)); - } - - function sort(address[] memory _keys, uint256[] memory _values) internal pure returns (address[] memory) { - require(_values.length == _keys.length, "Sorting: invalid array length"); - if (_keys.length == 0) { - return _keys; - } - - Node[] memory _nodes = new Node[](_keys.length); - for (uint256 _i; _i < _nodes.length; _i++) { - _nodes[_i] = Node(uint256(uint160(_keys[_i])), _values[_i]); - } - _quickSortNodes(_nodes, int(0), int(_nodes.length - 1)); - - for (uint256 _i; _i < _nodes.length; _i++) { - _keys[_i] = address(uint160(_nodes[_i].key)); // Casting? - } - - return _keys; - } - - function _quickSort( - uint[] memory arr, - int left, - int right - ) private pure returns (uint[] memory) { - int i = left; - int j = right; - if (i == j) return arr; - uint pivot = arr[uint(left + (right - left) / 2)]; - while (i <= j) { - while (arr[uint(i)] > pivot) i++; - while (pivot > arr[uint(j)]) j--; - if (i <= j) { - (arr[uint(i)], arr[uint(j)]) = (arr[uint(j)], arr[uint(i)]); - i++; - j--; - } - } - if (left < j) arr = _quickSort(arr, left, j); - if (i < right) arr = _quickSort(arr, i, right); - - return arr; - } - - function _quickSortNodes( - Node[] memory nodes, - int left, - int right - ) private pure returns (Node[] memory) { - int i = left; - int j = right; - if (i == j) return nodes; - Node memory pivot = nodes[uint(left + (right - left) / 2)]; - while (i <= j) { - while (nodes[uint(i)].value > pivot.value) i++; - while (pivot.value > nodes[uint(j)].value) j--; - if (i <= j) { - (nodes[uint(i)], nodes[uint(j)]) = __swapNodes(nodes[uint(i)], nodes[uint(j)]); - i++; - j--; - } - } - if (left < j) nodes = _quickSortNodes(nodes, left, j); - if (i < right) nodes = _quickSortNodes(nodes, i, right); - - return nodes; - } - - function _bubbleSortNodes(Node[] memory nodes) private pure returns (Node[] memory) { - uint length = nodes.length; - for (uint i = 0; i < length - 1; i++) { - for (uint j = i + 1; j < length; j++) { - if (nodes[j].value > nodes[i].value) { - (nodes[i], nodes[j]) = __swapNodes(nodes[i], nodes[j]); - } - } - } - return nodes; - } - - function __swapNodes(Node memory x, Node memory y) private pure returns (Node memory, Node memory) { - Node memory tmp = x; - (x, y) = (y, tmp); - return (x, y); - } -} diff --git a/contracts/mocks/MockPrecompile.sol b/contracts/mocks/MockPrecompile.sol index fac64d784..8932be402 100644 --- a/contracts/mocks/MockPrecompile.sol +++ b/contracts/mocks/MockPrecompile.sol @@ -2,11 +2,12 @@ pragma solidity ^0.8.9; -import "../libraries/Sorting.sol"; +import "./libraries/Sorting.sol"; +import "../libraries/Math.sol"; contract MockPrecompile { function sortValidators(address[] memory _validators, uint256[] memory _weights) - external + public pure returns (address[] memory) { @@ -16,7 +17,52 @@ contract MockPrecompile { function validatingDoubleSignProof( bytes calldata, /*_header1*/ bytes calldata /*_header2*/ - ) external pure returns (bool _validEvidence) { + ) public pure returns (bool _validEvidence) { return true; } + + function pickValidatorSet( + address[] memory _candidates, + uint256[] memory _balanceWeights, + uint256[] memory _trustedWeights, + uint256 _maxValidatorNumber, + uint256 _maxPrioritizedValidatorNumber + ) public pure returns (address[] memory _result) { + (_result, _trustedWeights) = Sorting.sortWithExternalKeys(_candidates, _balanceWeights, _trustedWeights); + uint256 _newValidatorCount = Math.min(_maxValidatorNumber, _result.length); + _arrangeValidatorCandidates(_result, _trustedWeights, _newValidatorCount, _maxPrioritizedValidatorNumber); + } + + /** + * @dev Arranges the sorted candidates to list of validators, by asserting prioritized and non-prioritized candidates + * + * @param _candidates A sorted list of candidates + */ + function _arrangeValidatorCandidates( + address[] memory _candidates, + uint256[] memory _trustedWeights, + uint _newValidatorCount, + uint _maxPrioritizedValidatorNumber + ) internal pure { + address[] memory _waitingCandidates = new address[](_candidates.length); + uint _waitingCounter; + uint _prioritySlotCounter; + + for (uint _i = 0; _i < _candidates.length; _i++) { + if (_trustedWeights[_i] > 0 && _prioritySlotCounter < _maxPrioritizedValidatorNumber) { + _candidates[_prioritySlotCounter++] = _candidates[_i]; + continue; + } + _waitingCandidates[_waitingCounter++] = _candidates[_i]; + } + + _waitingCounter = 0; + for (uint _i = _prioritySlotCounter; _i < _newValidatorCount; _i++) { + _candidates[_i] = _waitingCandidates[_waitingCounter++]; + } + + assembly { + mstore(_candidates, _newValidatorCount) + } + } } diff --git a/contracts/mocks/MockRoninValidatorSetExtended.sol b/contracts/mocks/MockRoninValidatorSetExtended.sol index 42c14d66c..3886ead96 100644 --- a/contracts/mocks/MockRoninValidatorSetExtended.sol +++ b/contracts/mocks/MockRoninValidatorSetExtended.sol @@ -2,10 +2,11 @@ pragma solidity ^0.8.9; -import "../libraries/Sorting.sol"; -import "../ronin/validator/RoninValidatorSet.sol"; +import "./MockRoninValidatorSetOverridePrecompile.sol"; +import "./libraries/Sorting.sol"; +import "../libraries/EnumFlags.sol"; -contract MockRoninValidatorSetExtended is RoninValidatorSet { +contract MockRoninValidatorSetExtended is MockRoninValidatorSetOverridePrecompile { uint256[] internal _epochs; uint256[] internal _periods; @@ -62,31 +63,8 @@ contract MockRoninValidatorSetExtended is RoninValidatorSet { function addValidators(address[] calldata _addrs) public { for (uint _i = 0; _i < _addrs.length; _i++) { - _validator[_i] = _addrs[_i]; - _validatorMap[_addrs[_i]] = true; + _validators[_i] = _addrs[_i]; + _validatorMap[_addrs[_i]] = EnumFlags.ValidatorFlag.Both; } } - - function arrangeValidatorCandidates(address[] memory _candidates, uint _newValidatorCount) - external - view - returns (address[] memory) - { - _arrangeValidatorCandidates(_candidates, _newValidatorCount); - - assembly { - mstore(_candidates, _newValidatorCount) - } - - return _candidates; - } - - function _sortCandidates(address[] memory _candidates, uint256[] memory _weights) - internal - pure - override - returns (address[] memory _result) - { - return Sorting.sort(_candidates, _weights); - } } diff --git a/contracts/mocks/MockRoninValidatorSetOverridePrecompile.sol b/contracts/mocks/MockRoninValidatorSetOverridePrecompile.sol new file mode 100644 index 000000000..4c34bc092 --- /dev/null +++ b/contracts/mocks/MockRoninValidatorSetOverridePrecompile.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "./MockPrecompile.sol"; +import "./libraries/Sorting.sol"; +import "../ronin/validator/RoninValidatorSet.sol"; + +contract MockRoninValidatorSetOverridePrecompile is RoninValidatorSet, MockPrecompile { + constructor() {} + + function arrangeValidatorCandidates( + address[] memory _candidates, + uint256[] memory _trustedWeights, + uint _newValidatorCount, + uint _maxPrioritizedValidatorNumber + ) external pure returns (address[] memory) { + _arrangeValidatorCandidates(_candidates, _trustedWeights, _newValidatorCount, _maxPrioritizedValidatorNumber); + return _candidates; + } + + function _pcSortCandidates(address[] memory _candidates, uint256[] memory _weights) + internal + pure + override + returns (address[] memory _result) + { + return sortValidators(_candidates, _weights); + } + + function _pcPickValidatorSet( + address[] memory _candidates, + uint256[] memory _balanceWeights, + uint256[] memory _trustedWeights, + uint256 _maxValidatorNumber, + uint256 _maxPrioritizedValidatorNumber + ) internal pure override returns (address[] memory _result, uint256 _newValidatorCount) { + _result = pickValidatorSet( + _candidates, + _balanceWeights, + _trustedWeights, + _maxValidatorNumber, + _maxPrioritizedValidatorNumber + ); + + _newValidatorCount = _result.length; + } +} diff --git a/contracts/mocks/MockRoninValidatorSetSorting.sol b/contracts/mocks/MockRoninValidatorSetSorting.sol deleted file mode 100644 index 959ca9010..000000000 --- a/contracts/mocks/MockRoninValidatorSetSorting.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -import "../ronin/validator/RoninValidatorSet.sol"; -import "../libraries/Sorting.sol"; - -contract MockRoninValidatorSetSorting is RoninValidatorSet { - constructor() {} - - function _sortCandidates(address[] memory _candidates, uint256[] memory _weights) - internal - pure - override - returns (address[] memory _result) - { - return Sorting.sort(_candidates, _weights); - } -} diff --git a/contracts/mocks/MockSlashIndicatorExtended.sol b/contracts/mocks/MockSlashIndicatorExtended.sol index d191676d1..696a53662 100644 --- a/contracts/mocks/MockSlashIndicatorExtended.sol +++ b/contracts/mocks/MockSlashIndicatorExtended.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.9; +import "./MockPrecompile.sol"; import "../ronin/SlashIndicator.sol"; -contract MockSlashIndicatorExtended is SlashIndicator { +contract MockSlashIndicatorExtended is SlashIndicator, MockPrecompile { function slashFelony(address _validatorAddr) external { _validatorContract.slash(_validatorAddr, 0, 0); } @@ -13,10 +14,12 @@ contract MockSlashIndicatorExtended is SlashIndicator { _validatorContract.slash(_validatorAddr, 0, 0); } - function _validateEvidence( - bytes calldata, /*_header1*/ - bytes calldata /*_header2*/ - ) internal pure override returns (bool _validEvidence) { - return true; + function _pcValidateEvidence(bytes calldata _header1, bytes calldata _header2) + internal + pure + override + returns (bool _validEvidence) + { + return validatingDoubleSignProof(_header1, _header2); } } diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index 48365da73..8d1a273ef 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -118,4 +118,20 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { function numberOfBlocksInEpoch() public view override(CandidateManager, ICandidateManager) returns (uint256) { return _numberOfBlocksInEpoch; } + + function getBridgeOperators() external view override returns (address[] memory) {} + + function isBridgeOperator(address) external pure override returns (bool) { + return true; + } + + function totalBridgeOperators() external view override returns (uint256) {} + + function getBlockProducers() external view override returns (address[] memory) {} + + function isBlockProducer(address) external pure override returns (bool) { + return true; + } + + function totalBlockProducers() external view override returns (uint256) {} } diff --git a/contracts/mocks/libraries/Sorting.sol b/contracts/mocks/libraries/Sorting.sol new file mode 100644 index 000000000..703983b3e --- /dev/null +++ b/contracts/mocks/libraries/Sorting.sol @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.0; + +library Sorting { + struct Node { + uint key; + uint value; + } + + struct Node3 { + uint key; + uint value; + uint otherKey; + } + + /////////////////////////////////////////////////////////////////////////////////////// + // VALUE SORTING // + /////////////////////////////////////////////////////////////////////////////////////// + + function sort(uint[] memory data) internal pure returns (uint[] memory) { + return _quickSort(data, int(0), int(data.length - 1)); + } + + function _quickSort( + uint[] memory arr, + int left, + int right + ) private pure returns (uint[] memory) { + int i = left; + int j = right; + if (i == j) return arr; + uint pivot = arr[uint(left + (right - left) / 2)]; + while (i <= j) { + while (arr[uint(i)] > pivot) i++; + while (pivot > arr[uint(j)]) j--; + if (i <= j) { + (arr[uint(i)], arr[uint(j)]) = (arr[uint(j)], arr[uint(i)]); + i++; + j--; + } + } + if (left < j) arr = _quickSort(arr, left, j); + if (i < right) arr = _quickSort(arr, i, right); + + return arr; + } + + /////////////////////////////////////////////////////////////////////////////////////// + // NODE SORTING // + /////////////////////////////////////////////////////////////////////////////////////// + + function sort(address[] memory _keys, uint256[] memory _values) internal pure returns (address[] memory) { + require(_values.length == _keys.length, "Sorting: invalid array length"); + if (_keys.length == 0) { + return _keys; + } + + Node[] memory _nodes = new Node[](_keys.length); + for (uint256 _i; _i < _nodes.length; _i++) { + _nodes[_i] = Node(uint256(uint160(_keys[_i])), _values[_i]); + } + _quickSortNodes(_nodes, int(0), int(_nodes.length - 1)); + + for (uint256 _i; _i < _nodes.length; _i++) { + _keys[_i] = address(uint160(_nodes[_i].key)); // Casting? + } + + return _keys; + } + + function sortNodes(Node[] memory nodes) internal pure returns (Node[] memory) { + return _quickSortNodes(nodes, int(0), int(nodes.length - 1)); + } + + function _quickSortNodes( + Node[] memory nodes, + int left, + int right + ) private pure returns (Node[] memory) { + int i = left; + int j = right; + if (i == j) return nodes; + Node memory pivot = nodes[uint(left + (right - left) / 2)]; + while (i <= j) { + while (nodes[uint(i)].value > pivot.value) i++; + while (pivot.value > nodes[uint(j)].value) j--; + if (i <= j) { + (nodes[uint(i)], nodes[uint(j)]) = __swapNodes(nodes[uint(i)], nodes[uint(j)]); + i++; + j--; + } + } + if (left < j) nodes = _quickSortNodes(nodes, left, j); + if (i < right) nodes = _quickSortNodes(nodes, i, right); + + return nodes; + } + + function _bubbleSortNodes(Node[] memory nodes) private pure returns (Node[] memory) { + uint length = nodes.length; + for (uint i = 0; i < length - 1; i++) { + for (uint j = i + 1; j < length; j++) { + if (nodes[j].value > nodes[i].value) { + (nodes[i], nodes[j]) = __swapNodes(nodes[i], nodes[j]); + } + } + } + return nodes; + } + + function __swapNodes(Node memory x, Node memory y) private pure returns (Node memory, Node memory) { + Node memory tmp = x; + (x, y) = (y, tmp); + return (x, y); + } + + /////////////////////////////////////////////////////////////////////////////////////// + // NODE3 SORTING // + /////////////////////////////////////////////////////////////////////////////////////// + + function sortWithExternalKeys( + address[] memory _keys, + uint256[] memory _values, + uint256[] memory _otherKeys + ) internal pure returns (address[] memory keys_, uint256[] memory otherKeys_) { + require((_values.length == _keys.length) && (_otherKeys.length == _keys.length), "Sorting: invalid array length"); + if (_keys.length == 0) { + return (_keys, _otherKeys); + } + + Node3[] memory _nodes = new Node3[](_keys.length); + for (uint256 _i; _i < _nodes.length; _i++) { + _nodes[_i] = Node3(uint256(uint160(_keys[_i])), _values[_i], _otherKeys[_i]); + } + _quickSortNode3s(_nodes, int(0), int(_nodes.length - 1)); + + for (uint256 _i; _i < _nodes.length; _i++) { + _keys[_i] = address(uint160(_nodes[_i].key)); // Casting? + } + + return (_keys, _otherKeys); + } + + function sortNode3s(Node3[] memory nodes) internal pure returns (Node3[] memory) { + return _quickSortNode3s(nodes, int(0), int(nodes.length - 1)); + } + + function _quickSortNode3s( + Node3[] memory nodes, + int left, + int right + ) private pure returns (Node3[] memory) { + int i = left; + int j = right; + if (i == j) return nodes; + Node3 memory pivot = nodes[uint(left + (right - left) / 2)]; + while (i <= j) { + while (nodes[uint(i)].value > pivot.value) i++; + while (pivot.value > nodes[uint(j)].value) j--; + if (i <= j) { + (nodes[uint(i)], nodes[uint(j)]) = __swapNode3s(nodes[uint(i)], nodes[uint(j)]); + i++; + j--; + } + } + if (left < j) nodes = _quickSortNode3s(nodes, left, j); + if (i < right) nodes = _quickSortNode3s(nodes, i, right); + + return nodes; + } + + function _bubbleSortNode3s(Node3[] memory nodes) private pure returns (Node3[] memory) { + uint length = nodes.length; + for (uint i = 0; i < length - 1; i++) { + for (uint j = i + 1; j < length; j++) { + if (nodes[j].value > nodes[i].value) { + (nodes[i], nodes[j]) = __swapNode3s(nodes[i], nodes[j]); + } + } + } + return nodes; + } + + function __swapNode3s(Node3 memory x, Node3 memory y) private pure returns (Node3 memory, Node3 memory) { + Node3 memory tmp = x; + (x, y) = (y, tmp); + return (x, y); + } +} diff --git a/contracts/mocks/precompile-usages/MockPrecompileUsagePickValidatorSet.sol b/contracts/mocks/precompile-usages/MockPrecompileUsagePickValidatorSet.sol new file mode 100644 index 000000000..8b742ed18 --- /dev/null +++ b/contracts/mocks/precompile-usages/MockPrecompileUsagePickValidatorSet.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../../precompile-usages/PrecompileUsagePickValidatorSet.sol"; + +contract MockPrecompileUsagePickValidatorSet is PrecompileUsagePickValidatorSet { + address internal _precompileSortValidatorAddress; + + constructor(address _precompile) { + setPrecompileSortValidatorAddress(_precompile); + } + + function setPrecompileSortValidatorAddress(address _addr) public { + _precompileSortValidatorAddress = _addr; + } + + function precompilePickValidatorSetAddress() public view override returns (address) { + return _precompileSortValidatorAddress; + } + + function callPrecompile( + address[] memory _candidates, + uint256[] memory _balanceWeights, + uint256[] memory _trustedWeights, + uint256 _maxValidatorNumber, + uint256 _maxPrioritizedValidatorNumber + ) public view returns (address[] memory _result) { + (_result, ) = _pcPickValidatorSet( + _candidates, + _balanceWeights, + _trustedWeights, + _maxValidatorNumber, + _maxPrioritizedValidatorNumber + ); + } +} diff --git a/contracts/mocks/precompile-usages/MockPrecompileUsageSortValidators.sol b/contracts/mocks/precompile-usages/MockPrecompileUsageSortValidators.sol index 8a52127a9..83dad44f6 100644 --- a/contracts/mocks/precompile-usages/MockPrecompileUsageSortValidators.sol +++ b/contracts/mocks/precompile-usages/MockPrecompileUsageSortValidators.sol @@ -24,6 +24,6 @@ contract MockPrecompileUsageSortValidators is PrecompileUsageSortValidators { view returns (address[] memory _result) { - return _sortCandidates(_validators, _weights); + return _pcSortCandidates(_validators, _weights); } } diff --git a/contracts/mocks/precompile-usages/MockPrecompileUsageValidateDoubleSign.sol b/contracts/mocks/precompile-usages/MockPrecompileUsageValidateDoubleSign.sol index 0d70e11d5..8475820a5 100644 --- a/contracts/mocks/precompile-usages/MockPrecompileUsageValidateDoubleSign.sol +++ b/contracts/mocks/precompile-usages/MockPrecompileUsageValidateDoubleSign.sol @@ -20,6 +20,6 @@ contract MockPrecompileUsageValidateDoubleSign is PrecompileUsageValidateDoubleS } function callPrecompile(bytes calldata _header1, bytes calldata _header2) public view returns (bool) { - return _validateEvidence(_header1, _header2); + return _pcValidateEvidence(_header1, _header2); } } diff --git a/contracts/mocks/sorting/MockSorting.sol b/contracts/mocks/sorting/MockSorting.sol index 98a700c2a..70742dbd2 100644 --- a/contracts/mocks/sorting/MockSorting.sol +++ b/contracts/mocks/sorting/MockSorting.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import "../../libraries/Sorting.sol"; +import "../libraries/Sorting.sol"; contract MockSorting { uint256[] public data; diff --git a/contracts/precompile-usages/PrecompileUsagePickValidatorSet.sol b/contracts/precompile-usages/PrecompileUsagePickValidatorSet.sol new file mode 100644 index 000000000..4b87a96ce --- /dev/null +++ b/contracts/precompile-usages/PrecompileUsagePickValidatorSet.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +abstract contract PrecompileUsagePickValidatorSet { + /// @dev Gets the address of the precompile of picking validator set + function precompilePickValidatorSetAddress() public view virtual returns (address) { + return address(0x68); + } + + /** + * @dev Sorts and arranges to return a new validator set. + * + * Note: The recover process is done by pre-compiled contract. This function is marked as + * virtual for implementing mocking contract for testing purpose. + */ + function _pcPickValidatorSet( + address[] memory _candidates, + uint256[] memory _balanceWeights, + uint256[] memory _trustedWeights, + uint256 _maxValidatorNumber, + uint256 _maxPrioritizedValidatorNumber + ) internal view virtual returns (address[] memory _result, uint256 _newValidatorCount) { + address _smc = precompilePickValidatorSetAddress(); + bytes memory _payload = abi.encodeWithSignature( + "pickValidatorSet(address[],uint256[],uint256[],uint256,uint256)", + _candidates, + _balanceWeights, + _trustedWeights, + _maxValidatorNumber, + _maxPrioritizedValidatorNumber + ); + bool _success = true; + + uint256 _payloadLength = _payload.length; + uint256 _resultLength = 0x20 * _candidates.length + 0x40; + + assembly { + let _payloadStart := add(_payload, 0x20) + + if iszero(staticcall(gas(), _smc, _payloadStart, _payloadLength, _result, _resultLength)) { + _success := 0 + } + + if iszero(returndatasize()) { + _success := 0 + } + + _result := add(_result, 0x20) + } + + require(_success, "PrecompileUsagePickValidatorSet: call to precompile fails"); + + _newValidatorCount = _result.length; + } +} diff --git a/contracts/precompile-usages/PrecompileUsageSortValidators.sol b/contracts/precompile-usages/PrecompileUsageSortValidators.sol index 91fa919fd..b9ab6168f 100644 --- a/contracts/precompile-usages/PrecompileUsageSortValidators.sol +++ b/contracts/precompile-usages/PrecompileUsageSortValidators.sol @@ -4,14 +4,16 @@ pragma solidity ^0.8.9; abstract contract PrecompileUsageSortValidators { /// @dev Gets the address of the precompile of sorting validators - function precompileSortValidatorsAddress() public view virtual returns (address); + function precompileSortValidatorsAddress() public view virtual returns (address) { + return address(0x66); + } /** - * @dev Sorting candidates descending by their weights by calling precompile contract. + * @dev Sorts candidates descending by their weights by calling precompile contract. * * Note: This function is marked as virtual for being wrapping in mock contract for testing purpose. */ - function _sortCandidates(address[] memory _candidates, uint256[] memory _weights) + function _pcSortCandidates(address[] memory _candidates, uint256[] memory _weights) internal view virtual diff --git a/contracts/precompile-usages/PrecompileUsageValidateDoubleSign.sol b/contracts/precompile-usages/PrecompileUsageValidateDoubleSign.sol index c1b81c6b3..216abb6de 100644 --- a/contracts/precompile-usages/PrecompileUsageValidateDoubleSign.sol +++ b/contracts/precompile-usages/PrecompileUsageValidateDoubleSign.sol @@ -4,15 +4,17 @@ pragma solidity ^0.8.9; abstract contract PrecompileUsageValidateDoubleSign { /// @dev Gets the address of the precompile of validating double sign evidence - function precompileValidateDoubleSignAddress() public view virtual returns (address); + function precompileValidateDoubleSignAddress() public view virtual returns (address) { + return address(0x67); + } /** - * @dev Validate the two submitted block header if they are produced by the same address + * @dev Validates the two submitted block header if they are produced by the same address * * Note: The recover process is done by pre-compiled contract. This function is marked as * virtual for implementing mocking contract for testing purpose. */ - function _validateEvidence(bytes calldata _header1, bytes calldata _header2) + function _pcValidateEvidence(bytes calldata _header1, bytes calldata _header2) internal view virtual diff --git a/contracts/ronin/Maintenance.sol b/contracts/ronin/Maintenance.sol index 5411f5eb9..5d2aa7e24 100644 --- a/contracts/ronin/Maintenance.sol +++ b/contracts/ronin/Maintenance.sol @@ -64,7 +64,7 @@ contract Maintenance is IMaintenance, HasValidatorContract, Initializable { ) external override { IRoninValidatorSet _validator = _validatorContract; - require(_validator.isValidator(_consensusAddr), "Maintenance: consensus address must be a validator"); + require(_validator.isBlockProducer(_consensusAddr), "Maintenance: consensus address must be a block producer"); require( _validator.isCandidateAdmin(_consensusAddr, msg.sender), "Maintenance: method caller must be a candidate admin" diff --git a/contracts/ronin/SlashIndicator.sol b/contracts/ronin/SlashIndicator.sol index 19a4c9604..6a220f5e8 100644 --- a/contracts/ronin/SlashIndicator.sol +++ b/contracts/ronin/SlashIndicator.sol @@ -18,9 +18,6 @@ contract SlashIndicator is { using Math for uint256; - /// @dev The address of the precompile of sorting validators - address internal constant _precompileValidateDoubleSignAddress = address(0x67); - /// @dev Mapping from validator address => period index => unavailability indicator mapping(address => mapping(uint256 => uint256)) internal _unavailabilityIndicator; /// @dev Maping from validator address => period index => slash type @@ -135,7 +132,7 @@ contract SlashIndicator is return; } - if (_validateEvidence(_header1, _header2)) { + if (_pcValidateEvidence(_header1, _header2)) { uint256 _period = _validatorContract.periodOf(block.number); _unavailabilitySlashed[_validatorAddr][_period] = SlashType.DOUBLE_SIGNING; emit UnavailabilitySlashed(_validatorAddr, SlashType.DOUBLE_SIGNING, _period); @@ -235,13 +232,6 @@ contract SlashIndicator is return _unavailabilityIndicator[_validator][_period]; } - /** - * @inheritdoc PrecompileUsageValidateDoubleSign - */ - function precompileValidateDoubleSignAddress() public pure override returns (address) { - return _precompileValidateDoubleSignAddress; - } - /////////////////////////////////////////////////////////////////////////////////////// // HELPER FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// @@ -301,7 +291,7 @@ contract SlashIndicator is function _shouldSlash(address _addr) internal view returns (bool) { return (msg.sender != _addr) && - _validatorContract.isValidator(_addr) && + _validatorContract.isBlockProducer(_addr) && !_maintenanceContract.maintaining(_addr, block.number); } } diff --git a/contracts/ronin/StakingVesting.sol b/contracts/ronin/StakingVesting.sol index 7921a7c9c..0afa2562c 100644 --- a/contracts/ronin/StakingVesting.sol +++ b/contracts/ronin/StakingVesting.sol @@ -8,10 +8,14 @@ import "../extensions/collections/HasValidatorContract.sol"; import "../extensions/RONTransferHelper.sol"; contract StakingVesting is IStakingVesting, HasValidatorContract, RONTransferHelper, Initializable { - /// @dev The block bonus whenever a new block is mined. - uint256 internal _bonusPerBlock; - /// @dev The last block number that the bonus reward sent. - uint256 public lastBonusSentBlock; + /// @dev The block bonus for the validator whenever a new block is mined. + uint256 internal _validatorBonusPerBlock; + /// @dev The block bonus for the bridge operator whenever a new block is mined. + uint256 internal _bridgeOperatorBonusPerBlock; + /// @dev The last block number that the staking vesting for validator sent. + uint256 public lastBlockSendingValidatorBonus; + /// @dev The last block number that the staking vesting for bridge operator sent. + uint256 public lastBlockSendingBridgeOperatorBonus; constructor() { _disableInitializers(); @@ -20,9 +24,14 @@ contract StakingVesting is IStakingVesting, HasValidatorContract, RONTransferHel /** * @dev Initializes the contract storage. */ - function initialize(address __validatorContract, uint256 __bonusPerBlock) external payable initializer { + function initialize( + address __validatorContract, + uint256 __validatorBonusPerBlock, + uint256 __bridgeOperatorBonusPerBlock + ) external payable initializer { _setValidatorContract(__validatorContract); - _setBonusPerBlock(__bonusPerBlock); + _setValidatorBonusPerBlock(__validatorBonusPerBlock); + _setBridgeOperatorBonusPerBlock(__bridgeOperatorBonusPerBlock); } /** @@ -33,21 +42,61 @@ contract StakingVesting is IStakingVesting, HasValidatorContract, RONTransferHel /** * @inheritdoc IStakingVesting */ - function blockBonus( + function validatorBlockBonus( uint256 /* _block */ ) public view returns (uint256) { - return _bonusPerBlock; + return _validatorBonusPerBlock; } /** * @inheritdoc IStakingVesting */ - function requestBlockBonus() external onlyValidatorContract returns (uint256 _amount) { - uint256 _block = block.number; + function bridgeOperatorBlockBonus( + uint256 /* _block */ + ) public view returns (uint256) { + return _bridgeOperatorBonusPerBlock; + } + + /** + * @inheritdoc IStakingVesting + */ + function requestBonus() + external + onlyValidatorContract + returns (uint256 _validatorBonus, uint256 _bridgeOperatorBonus) + { + _validatorBonus = requestValidatorBonus(); + _bridgeOperatorBonus = requestBridgeOperatorBonus(); + } + + /** + * @inheritdoc IStakingVesting + */ + function requestValidatorBonus() public onlyValidatorContract returns (uint256 _amount) { + require(block.number > lastBlockSendingValidatorBonus, "StakingVesting: bonus for validator already sent"); + lastBlockSendingValidatorBonus = block.number; + _amount = validatorBlockBonus(block.number); + + if (_amount > 0) { + address payable _validatorContractAddr = payable(validatorContract()); + require( + _sendRON(_validatorContractAddr, _amount), + "StakingVesting: could not transfer RON to validator contract" + ); + emit ValidatorBonusTransferred(block.number, _validatorContractAddr, _amount); + } + } - require(_block > lastBonusSentBlock, "StakingVesting: bonus already sent"); - lastBonusSentBlock = _block; - _amount = blockBonus(_block); + /** + * @inheritdoc IStakingVesting + */ + function requestBridgeOperatorBonus() public onlyValidatorContract returns (uint256 _amount) { + require( + block.number > lastBlockSendingBridgeOperatorBonus, + "StakingVesting: bonus for bridge operator already sent" + ); + lastBlockSendingBridgeOperatorBonus = block.number; + _amount = bridgeOperatorBlockBonus(block.number); if (_amount > 0) { address payable _validatorContractAddr = payable(validatorContract()); @@ -55,18 +104,29 @@ contract StakingVesting is IStakingVesting, HasValidatorContract, RONTransferHel _sendRON(_validatorContractAddr, _amount), "StakingVesting: could not transfer RON to validator contract" ); - emit BlockBonusTransferred(_block, _validatorContractAddr, _amount); + emit BridgeOperatorBonusTransferred(block.number, _validatorContractAddr, _amount); } } /** - * @dev Sets the bonus amount per block. + * @dev Sets the bonus amount per block for validator. + * + * Emits the event `ValidatorBonusPerBlockUpdated`. + * + */ + function _setValidatorBonusPerBlock(uint256 _amount) internal { + _validatorBonusPerBlock = _amount; + emit ValidatorBonusPerBlockUpdated(_amount); + } + + /** + * @dev Sets the bonus amount per block for bridge operator. * - * Emits the event `BonusPerBlockUpdated`. + * Emits the event `BridgeOperatorBonusPerBlockUpdated`. * */ - function _setBonusPerBlock(uint256 _amount) internal { - _bonusPerBlock = _amount; - emit BonusPerBlockUpdated(_amount); + function _setBridgeOperatorBonusPerBlock(uint256 _amount) internal { + _bridgeOperatorBonusPerBlock = _amount; + emit BridgeOperatorBonusPerBlockUpdated(_amount); } } diff --git a/contracts/ronin/staking/Staking.sol b/contracts/ronin/staking/Staking.sol index 84b361613..fc9f3a23f 100644 --- a/contracts/ronin/staking/Staking.sol +++ b/contracts/ronin/staking/Staking.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "../../interfaces/IStaking.sol"; import "../../interfaces/IRoninValidatorSet.sol"; -import "../../libraries/Sorting.sol"; import "./StakingManager.sol"; contract Staking is IStaking, StakingManager, Initializable { diff --git a/contracts/ronin/staking/StakingManager.sol b/contracts/ronin/staking/StakingManager.sol index 973d65a2f..1bbb400e5 100644 --- a/contracts/ronin/staking/StakingManager.sol +++ b/contracts/ronin/staking/StakingManager.sol @@ -100,11 +100,20 @@ abstract contract StakingManager is address _candidateAdmin, address _consensusAddr, address payable _treasuryAddr, + address _bridgeOperatorAddr, uint256 _commissionRate ) external payable override nonReentrant { uint256 _amount = msg.value; address payable _poolAdmin = payable(msg.sender); - _applyValidatorCandidate(_poolAdmin, _candidateAdmin, _consensusAddr, _treasuryAddr, _commissionRate, _amount); + _applyValidatorCandidate( + _poolAdmin, + _candidateAdmin, + _consensusAddr, + _treasuryAddr, + _bridgeOperatorAddr, + _commissionRate, + _amount + ); PoolDetail storage _pool = _stakingPool[_consensusAddr]; _pool.admin = _poolAdmin; @@ -162,6 +171,7 @@ abstract contract StakingManager is address _candidateAdmin, address _consensusAddr, address payable _treasuryAddr, + address _bridgeOperatorAddr, uint256 _commissionRate, uint256 _amount ) internal { @@ -169,7 +179,13 @@ abstract contract StakingManager is require(_sendRON(_treasuryAddr, 0), "StakingManager: treasury cannot receive RON"); require(_amount >= minValidatorBalance(), "StakingManager: insufficient amount"); - _validatorContract.grantValidatorCandidate(_candidateAdmin, _consensusAddr, _treasuryAddr, _commissionRate); + _validatorContract.grantValidatorCandidate( + _candidateAdmin, + _consensusAddr, + _treasuryAddr, + _bridgeOperatorAddr, + _commissionRate + ); } /** diff --git a/contracts/ronin/validator/CandidateManager.sol b/contracts/ronin/validator/CandidateManager.sol index 8d7a59e8b..6f6bd8bb3 100644 --- a/contracts/ronin/validator/CandidateManager.sol +++ b/contracts/ronin/validator/CandidateManager.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.9; import "../../extensions/collections/HasStakingContract.sol"; import "../../interfaces/ICandidateManager.sol"; import "../../interfaces/IStaking.sol"; -import "../../libraries/Sorting.sol"; abstract contract CandidateManager is ICandidateManager, HasStakingContract { /// @dev Maximum number of validator candidate @@ -39,6 +38,7 @@ abstract contract CandidateManager is ICandidateManager, HasStakingContract { address _admin, address _consensusAddr, address payable _treasuryAddr, + address _bridgeOperatorAddr, uint256 _commissionRate ) external override onlyStakingContract { uint256 _length = _candidates.length; @@ -51,11 +51,12 @@ abstract contract CandidateManager is ICandidateManager, HasStakingContract { _admin, _consensusAddr, _treasuryAddr, + _bridgeOperatorAddr, _commissionRate, type(uint256).max, new bytes(0) ); - emit CandidateGranted(_consensusAddr, _treasuryAddr, _admin); + emit CandidateGranted(_consensusAddr, _treasuryAddr, _admin, _bridgeOperatorAddr); } /** @@ -113,11 +114,13 @@ abstract contract CandidateManager is ICandidateManager, HasStakingContract { function numberOfBlocksInEpoch() public view virtual returns (uint256); /** - * @dev Removes unsastisfied candidates (the ones who have insufficient minimum candidate balance). - * Returns the total balance list of the new candidate list. + * @dev Removes unsastisfied candidates, the ones who have insufficient minimum candidate balance, + * or the ones who revoked their candidate role. * * Emits the event `CandidatesRevoked` when a candidate is revoked. * + * @return _balances a list of balances of the new candidates. + * */ function _filterUnsatisfiedCandidates(uint256 _minBalance) internal returns (uint256[] memory _balances) { IStaking _staking = _stakingContract; @@ -169,9 +172,12 @@ abstract contract CandidateManager is ICandidateManager, HasStakingContract { delete _candidateIndex[_addr]; address _lastCandidate = _candidates[_candidates.length - 1]; - _candidateIndex[_lastCandidate] = _idx; - _candidates[~_idx] = _lastCandidate; + if (_lastCandidate != _addr) { + _candidateIndex[_lastCandidate] = _idx; + _candidates[~_idx] = _lastCandidate; + } + _candidates.pop(); } diff --git a/contracts/ronin/validator/RoninValidatorSet.sol b/contracts/ronin/validator/RoninValidatorSet.sol index 3dcb5d0c9..d63687bb1 100644 --- a/contracts/ronin/validator/RoninValidatorSet.sol +++ b/contracts/ronin/validator/RoninValidatorSet.sol @@ -11,14 +11,16 @@ import "../../extensions/collections/HasSlashIndicatorContract.sol"; import "../../extensions/collections/HasMaintenanceContract.sol"; import "../../extensions/collections/HasRoninTrustedOrganizationContract.sol"; import "../../interfaces/IRoninValidatorSet.sol"; -import "../../libraries/Sorting.sol"; import "../../libraries/Math.sol"; +import "../../libraries/EnumFlags.sol"; import "../../precompile-usages/PrecompileUsageSortValidators.sol"; +import "../../precompile-usages/PrecompileUsagePickValidatorSet.sol"; import "./CandidateManager.sol"; contract RoninValidatorSet is IRoninValidatorSet, PrecompileUsageSortValidators, + PrecompileUsagePickValidatorSet, RONTransferHelper, HasStakingContract, HasStakingVestingContract, @@ -28,8 +30,8 @@ contract RoninValidatorSet is CandidateManager, Initializable { - /// @dev The address of the precompile of sorting validators - address internal constant _precompileSortValidatorAddress = address(0x66); + using EnumFlags for EnumFlags.ValidatorFlag; + /// @dev The maximum number of validator. uint256 internal _maxValidatorNumber; /// @dev The number of blocks in a epoch @@ -42,9 +44,9 @@ contract RoninValidatorSet is /// @dev The total of validators uint256 public validatorCount; /// @dev Mapping from validator index => validator address - mapping(uint256 => address) internal _validator; - /// @dev Mapping from address => flag indicating whether the address is validator or not - mapping(address => bool) internal _validatorMap; + mapping(uint256 => address) internal _validators; + /// @dev Mapping from address => flag indicating the validator ability: producing block, operating bridge + mapping(address => EnumFlags.ValidatorFlag) internal _validatorMap; /// @dev The number of slot that is reserved for prioritized validators uint256 internal _maxPrioritizedValidatorNumber; @@ -57,6 +59,8 @@ contract RoninValidatorSet is mapping(address => uint256) internal _miningReward; /// @dev Mapping from validator address => pending reward from delegating mapping(address => uint256) internal _delegatingReward; + /// @dev Mapping from validator address => pending reward for being bridge operator + mapping(address => uint256) internal _bridgeOperatingReward; modifier onlyCoinbase() { require(msg.sender == block.coinbase, "RoninValidatorSet: method caller must be coinbase"); @@ -123,24 +127,27 @@ contract RoninValidatorSet is address _coinbaseAddr = msg.sender; // Deprecates reward for non-validator or slashed validator if ( - !isValidator(_coinbaseAddr) || _jailed(_coinbaseAddr) || _rewardDeprecated(_coinbaseAddr, periodOf(block.number)) + !isBlockProducer(_coinbaseAddr) || + _jailed(_coinbaseAddr) || + _rewardDeprecated(_coinbaseAddr, periodOf(block.number)) ) { emit RewardDeprecated(_coinbaseAddr, _submittedReward); return; } - uint256 _bonusReward = _stakingVestingContract.requestBlockBonus(); - uint256 _reward = _submittedReward + _bonusReward; + (uint256 _validatorStakingVesting, uint256 _bridgeValidatorStakingVesting) = _stakingVestingContract.requestBonus(); + uint256 _reward = _submittedReward + _validatorStakingVesting; - IStaking _staking = IStaking(_stakingContract); + IStaking _staking = _stakingContract; uint256 _rate = _candidateInfo[_coinbaseAddr].commissionRate; uint256 _miningAmount = (_rate * _reward) / 100_00; uint256 _delegatingAmount = _reward - _miningAmount; _miningReward[_coinbaseAddr] += _miningAmount; _delegatingReward[_coinbaseAddr] += _delegatingAmount; + _bridgeOperatingReward[_coinbaseAddr] += _bridgeValidatorStakingVesting; _staking.recordReward(_coinbaseAddr, _delegatingAmount); - emit BlockRewardSubmitted(_coinbaseAddr, _submittedReward, _bonusReward); + emit BlockRewardSubmitted(_coinbaseAddr, _submittedReward, _validatorStakingVesting); } /** @@ -153,53 +160,25 @@ contract RoninValidatorSet is ); _lastUpdatedBlock = block.number; - IStaking _staking = IStaking(_stakingContract); - address _validatorAddr; - uint256 _delegatingAmount; + address[] memory _currentValidators = getValidators(); + uint256 _epoch = epochOf(block.number); uint256 _period = periodOf(block.number); bool _periodEnding = periodEndingAt(block.number); - address[] memory _validators = getValidators(); - for (uint _i = 0; _i < _validators.length; _i++) { - _validatorAddr = _validators[_i]; - - if (_jailed(_validatorAddr) || _rewardDeprecated(_validatorAddr, _period)) { - continue; - } - - if (_periodEnding) { - uint256 _miningAmount = _miningReward[_validatorAddr]; - delete _miningReward[_validatorAddr]; - if (_miningAmount > 0) { - address payable _treasury = _candidateInfo[_validatorAddr].treasuryAddr; - require(_sendRON(_treasury, _miningAmount), "RoninValidatorSet: could not transfer RON treasury address"); - emit MiningRewardDistributed(_validatorAddr, _miningAmount); - } - } + if (_periodEnding) { + uint256 _totalDelegatingReward = _distributeRewardToTreasuriesAndCalculateTotalDelegatingReward( + _currentValidators, + _period + ); - _delegatingAmount += _delegatingReward[_validatorAddr]; - delete _delegatingReward[_validatorAddr]; - } + _settleAndTransferDelegatingRewards(_currentValidators, _totalDelegatingReward); - if (_periodEnding) { - _staking.settleRewardPools(_validators); - if (_delegatingAmount > 0) { - require( - _sendRON(payable(address(_staking)), 0), - "RoninValidatorSet: could not transfer RON to staking contract" - ); - emit StakingRewardDistributed(_delegatingAmount); - } + _currentValidators = _syncValidatorSet(); } - _updateValidatorSet(_periodEnding); - } + _revampBlockProducers(_currentValidators); - /** - * @inheritdoc IRoninValidatorSet - */ - function getLastUpdatedBlock() external view returns (uint256) { - return _lastUpdatedBlock; + emit WrappedUpEpoch(_period, _epoch, _periodEnding); } /////////////////////////////////////////////////////////////////////////////////////// @@ -271,21 +250,94 @@ contract RoninValidatorSet is return _block == 0 ? 0 : _block / (_numberOfBlocksInEpoch * _numberOfEpochsInPeriod) + 1; } + /** + * @inheritdoc IRoninValidatorSet + */ + function getLastUpdatedBlock() external view returns (uint256) { + return _lastUpdatedBlock; + } + /** * @inheritdoc IRoninValidatorSet */ function getValidators() public view override returns (address[] memory _validatorList) { _validatorList = new address[](validatorCount); for (uint _i = 0; _i < _validatorList.length; _i++) { - _validatorList[_i] = _validator[_i]; + _validatorList[_i] = _validators[_i]; } } /** * @inheritdoc IRoninValidatorSet */ - function isValidator(address _addr) public view returns (bool) { - return _validatorMap[_addr]; + function isValidator(address _addr) public view override returns (bool) { + return !_validatorMap[_addr].isNone(); + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function getBlockProducers() public view override returns (address[] memory _result) { + _result = new address[](validatorCount); + uint256 _count = 0; + for (uint _i = 0; _i < _result.length; _i++) { + if (isBlockProducer(_validators[_i])) { + _result[_count++] = _validators[_i]; + } + } + + assembly { + mstore(_result, _count) + } + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function isBlockProducer(address _addr) public view override returns (bool) { + return _validatorMap[_addr].hasFlag(EnumFlags.ValidatorFlag.BlockProducer); + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function totalBlockProducers() external view returns (uint256 _total) { + for (uint _i = 0; _i < validatorCount; _i++) { + if (isBlockProducer(_validators[_i])) { + _total++; + } + } + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function getBridgeOperators() public view override returns (address[] memory _bridgeOperatorList) { + _bridgeOperatorList = new address[](validatorCount); + for (uint _i = 0; _i < _bridgeOperatorList.length; _i++) { + _bridgeOperatorList[_i] = _candidateInfo[_validators[_i]].bridgeOperatorAddr; + } + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function isBridgeOperator(address _bridgeOperatorAddr) external view override returns (bool _result) { + for (uint _i = 0; _i < validatorCount; _i++) { + if (_candidateInfo[_validators[_i]].bridgeOperatorAddr == _bridgeOperatorAddr) { + _result = true; + break; + } + } + } + + /** + * Notice: A validator is always a bride operator + * + * @inheritdoc IRoninValidatorSet + */ + function totalBridgeOperators() external view returns (uint256) { + return validatorCount; } /** @@ -341,13 +393,6 @@ contract RoninValidatorSet is return _maxPrioritizedValidatorNumber; } - /** - * @inheritdoc PrecompileUsageSortValidators - */ - function precompileSortValidatorsAddress() public pure override returns (address) { - return _precompileSortValidatorAddress; - } - /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR ADMIN // /////////////////////////////////////////////////////////////////////////////////////// @@ -374,79 +419,188 @@ contract RoninValidatorSet is } /////////////////////////////////////////////////////////////////////////////////////// - // HELPER FUNCTIONS // + // PRIVATE HELPER FUNCTIONS OF WRAPPING UP EPOCH // /////////////////////////////////////////////////////////////////////////////////////// /** - * @dev Returns validator candidates list. + * @dev This loop over the all current validators to: + * - Update delegating reward for and calculate total delegating rewards to be sent to the staking contract, + * - Distribute the reward of block producers and bridge operators to their treasury addresses. + * + * Note: This method should be called once in the end of each period. + * */ - function _syncNewValidatorSet(bool _periodEnding) internal returns (address[] memory _candidateList) { - // This is a temporary approach since the slashing issue is still not finalized. - // Read more about slashing issue at: https://www.notion.so/skymavis/Slashing-Issue-9610ae1452434faca1213ab2e1d7d944 - uint256 _minBalance = _stakingContract.minValidatorBalance(); - uint256[] memory _weights = _filterUnsatisfiedCandidates(_periodEnding ? _minBalance : 0); - _candidateList = _candidates; - - uint256 _length = _candidateList.length; - for (uint256 _i; _i < _candidateList.length; _i++) { - if (_jailed(_candidateList[_i]) || _weights[_i] < _minBalance) { - _length--; - _candidateList[_i] = _candidateList[_length]; - _weights[_i] = _weights[_length]; + function _distributeRewardToTreasuriesAndCalculateTotalDelegatingReward( + address[] memory _currentValidators, + uint256 _period + ) private returns (uint256 _totalDelegatingReward) { + for (uint _i = 0; _i < _currentValidators.length; _i++) { + address _validatorAddr = _currentValidators[_i]; + + if (_jailed(_validatorAddr) || _rewardDeprecated(_validatorAddr, _period)) { + continue; } + + _totalDelegatingReward += _delegatingReward[_validatorAddr]; + delete _delegatingReward[_validatorAddr]; + + _distributeRewardToTreasury(_validatorAddr); } + } - assembly { - mstore(_candidateList, _length) - mstore(_weights, _length) + /** + * @dev Distribute bonus of staking vesting and mining fee for block producer; and bonus of staking vesting for + * bridge oparators to the treasury address of a validator. + * + * Emits the `MiningRewardDistributed` event once the validator has an amount of mining reward. + * Emits the `BridgeOperatorRewardDistributed` once the validator has an amount of bridge reward. + * + * Note: This method should be called once in the end of each period. + * + */ + function _distributeRewardToTreasury(address _validatorAddr) private { + address payable _treasury = _candidateInfo[_validatorAddr].treasuryAddr; + + uint256 _miningAmount = _miningReward[_validatorAddr]; + if (_miningAmount > 0) { + delete _miningReward[_validatorAddr]; + require(_sendRON(_treasury, _miningAmount), "RoninValidatorSet: could not transfer RON treasury address"); + emit MiningRewardDistributed(_validatorAddr, _treasury, _miningAmount); } - _candidateList = _sortCandidates(_candidateList, _weights); + uint256 _bridgeOperatingAmount = _bridgeOperatingReward[_validatorAddr]; + if (_bridgeOperatingAmount > 0) { + delete _bridgeOperatingReward[_validatorAddr]; + require( + _sendRON(_treasury, _bridgeOperatingAmount), + "RoninValidatorSet: could not transfer RON to bridge operator address" + ); + emit BridgeOperatorRewardDistributed(_validatorAddr, _treasury, _bridgeOperatingAmount); + } + } + + /** + * @dev Helper function to settle rewards for delegators of `_currentValidators` at the end of each period, + * then transfer the rewards from this contract to the staking contract, in order to finalize a period. + * + * Emit `StakingRewardDistributed` event. + * + * Note: This method should be called once in the end of each period. + * + */ + function _settleAndTransferDelegatingRewards(address[] memory _currentValidators, uint256 _totalDelegatingReward) + private + { + IStaking _staking = _stakingContract; + + _staking.settleRewardPools(_currentValidators); + if (_totalDelegatingReward > 0) { + require( + _sendRON(payable(address(_staking)), _totalDelegatingReward), + "RoninValidatorSet: could not transfer RON to staking contract" + ); + emit StakingRewardDistributed(_totalDelegatingReward); + } } /** * @dev Updates the validator set based on the validator candidates from the Staking contract. * * Emits the `ValidatorSetUpdated` event. + * Emits the `BridgeOperatorSetUpdated` event. + * + * Note: This method should be called once in the end of each period. * */ - function _updateValidatorSet(bool _periodEnding) internal virtual { - address[] memory _candidates = _syncNewValidatorSet(_periodEnding); - uint256 _newValidatorCount = Math.min(_maxValidatorNumber, _candidates.length); - _arrangeValidatorCandidates(_candidates, _newValidatorCount); - - assembly { - mstore(_candidates, _newValidatorCount) - } + function _syncValidatorSet() private returns (address[] memory _newValidators) { + uint256[] memory _balanceWeights; + // This is a temporary approach since the slashing issue is still not finalized. + // Read more about slashing issue at: https://www.notion.so/skymavis/Slashing-Issue-9610ae1452434faca1213ab2e1d7d944 + uint256 _minBalance = _stakingContract.minValidatorBalance(); + _balanceWeights = _filterUnsatisfiedCandidates(_minBalance); + uint256[] memory _trustedWeights = _roninTrustedOrganizationContract.getWeights(_candidates); + uint256 _newValidatorCount; + (_newValidators, _newValidatorCount) = _pcPickValidatorSet( + _candidates, + _balanceWeights, + _trustedWeights, + _maxValidatorNumber, + _maxPrioritizedValidatorNumber + ); + _setNewValidatorSet(_newValidators, _newValidatorCount); + emit BridgeOperatorSetUpdated(getBridgeOperators()); + } + /** + * @dev Private helper function helps writing the new validator set into the contract storage. + * + * Emits the `ValidatorSetUpdated` event. + * + * Note: This method should be called once in the end of each period. + * + */ + function _setNewValidatorSet(address[] memory _newValidators, uint256 _newValidatorCount) private { for (uint256 _i = _newValidatorCount; _i < validatorCount; _i++) { - delete _validatorMap[_validator[_i]]; - delete _validator[_i]; + delete _validatorMap[_validators[_i]]; + delete _validators[_i]; } uint256 _count; - bool[] memory _maintainingList = _maintenanceContract.bulkMaintaining(_candidates, block.number + 1); for (uint256 _i = 0; _i < _newValidatorCount; _i++) { - if (_maintainingList[_i]) { - continue; - } - - address _newValidator = _candidates[_i]; - if (_newValidator == _validator[_count]) { + address _newValidator = _newValidators[_i]; + if (_newValidator == _validators[_count]) { _count++; continue; } - delete _validatorMap[_validator[_count]]; - _validatorMap[_newValidator] = true; - _validator[_count] = _newValidator; + delete _validatorMap[_validators[_count]]; + _validatorMap[_newValidator] = EnumFlags.ValidatorFlag.Both; + _validators[_count] = _newValidator; _count++; } validatorCount = _count; - emit ValidatorSetUpdated(_candidates); + emit ValidatorSetUpdated(_newValidators); } + /** + * @dev Activate/Deactivate the validators from producing blocks, based on their in jail status and maintenance status. + * + * Requirements: + * - This method is called at the end of each epoch + * + * Emits the `BlockProducerSetUpdated` event. + * + */ + function _revampBlockProducers(address[] memory _currentValidators) private { + bool[] memory _maintainingList = _maintenanceContract.bulkMaintaining(_candidates, block.number + 1); + + for (uint _i = 0; _i < _currentValidators.length; _i++) { + address _currentValidator = _currentValidators[_i]; + bool _isProducerBefore = isBlockProducer(_currentValidator); + bool _isProducerAfter = !(_jailed(_currentValidator) || _maintainingList[_i]); + + if (!_isProducerBefore && _isProducerAfter) { + _validatorMap[_currentValidator] = _validatorMap[_currentValidator].addFlag( + EnumFlags.ValidatorFlag.BlockProducer + ); + continue; + } + + if (_isProducerBefore && !_isProducerAfter) { + _validatorMap[_currentValidator] = _validatorMap[_currentValidator].removeFlag( + EnumFlags.ValidatorFlag.BlockProducer + ); + } + } + + emit BlockProducerSetUpdated(getBlockProducers()); + } + + /////////////////////////////////////////////////////////////////////////////////////// + // OTHER HELPER FUNCTIONS // + /////////////////////////////////////////////////////////////////////////////////////// + /** * @dev Returns whether the reward of the validator is put in jail (cannot join the set of validators) during the current period. */ @@ -461,38 +615,6 @@ contract RoninValidatorSet is return _rewardDeprecatedAtPeriod[_validatorAddr][_period]; } - /** - * @dev Returns whether the address `_addr` is validator or not. - */ - function _isValidator(address _addr) internal view returns (bool) { - return _validatorMap[_addr]; - } - - /** - * @dev Arranges the sorted candidates to list of validators, by asserting prioritized and non-prioritized candidates - * - * @param _candidates A sorted list of candidates - */ - function _arrangeValidatorCandidates(address[] memory _candidates, uint _newValidatorCount) internal view { - address[] memory _waitingCandidates = new address[](_candidates.length); - uint _waitingCounter; - uint _prioritySlotCounter; - - uint256[] memory _trustedWeights = _roninTrustedOrganizationContract.getWeights(_candidates); - for (uint _i = 0; _i < _candidates.length; _i++) { - if (_trustedWeights[_i] > 0 && _prioritySlotCounter < _maxPrioritizedValidatorNumber) { - _candidates[_prioritySlotCounter++] = _candidates[_i]; - continue; - } - _waitingCandidates[_waitingCounter++] = _candidates[_i]; - } - - _waitingCounter = 0; - for (uint _i = _prioritySlotCounter; _i < _newValidatorCount; _i++) { - _candidates[_i] = _waitingCandidates[_waitingCounter++]; - } - } - /** * @dev Updates the max validator number * diff --git a/src/config.ts b/src/config.ts index 82d3df393..d5988d129 100644 --- a/src/config.ts +++ b/src/config.ts @@ -72,7 +72,8 @@ export interface StakingConfig { } export interface StakingVestingArguments { - bonusPerBlock?: BigNumberish; + validatorBonusPerBlock?: BigNumberish; + bridgeOperatorBonusPerBlock?: BigNumberish; topupAmount?: BigNumberish; } @@ -152,7 +153,8 @@ export const stakingConfig: StakingConfig = { export const stakingVestingConfig: StakingVestingConfig = { [Network.Hardhat]: undefined, [Network.Devnet]: { - bonusPerBlock: BigNumber.from(10).pow(18), // 1 RON per block + validatorBonusPerBlock: BigNumber.from(10).pow(18), // 1 RON per block + bridgeOperatorBonusPerBlock: BigNumber.from(10).pow(18), // 1 RON per block topupAmount: BigNumber.from(10).pow(18).mul(BigNumber.from(10).pow(4)), // 10.000 RON }, [Network.Testnet]: undefined, diff --git a/src/deploy/logic/staking-vesting.ts b/src/deploy/logic/bonus-reward.ts similarity index 100% rename from src/deploy/logic/staking-vesting.ts rename to src/deploy/logic/bonus-reward.ts diff --git a/src/deploy/proxy/staking-vesting-proxy.ts b/src/deploy/proxy/bonus-reward-proxy.ts similarity index 91% rename from src/deploy/proxy/staking-vesting-proxy.ts rename to src/deploy/proxy/bonus-reward-proxy.ts index bb945f850..93166152d 100644 --- a/src/deploy/proxy/staking-vesting-proxy.ts +++ b/src/deploy/proxy/bonus-reward-proxy.ts @@ -18,7 +18,8 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const data = new StakingVesting__factory().interface.encodeFunctionData('initialize', [ roninInitAddress[network.name]!.validatorContract?.address, - stakingVestingConfig[network.name]!.bonusPerBlock, + stakingVestingConfig[network.name]!.validatorBonusPerBlock, + stakingVestingConfig[network.name]!.bridgeOperatorBonusPerBlock, ]); const deployment = await deploy('StakingVestingProxy', { diff --git a/test/helpers/candidate-manager.ts b/test/helpers/candidate-manager.ts new file mode 100644 index 000000000..dc76a07e1 --- /dev/null +++ b/test/helpers/candidate-manager.ts @@ -0,0 +1,44 @@ +import { ethers, network } from 'hardhat'; + +import { expect } from 'chai'; +import { ContractTransaction } from 'ethers'; + +import { expectEvent } from './utils'; +import { CandidateManager__factory } from '../../src/types'; + +const contractInterface = CandidateManager__factory.createInterface(); + +export const expects = { + emitCandidatesRevokedEvent: async function (tx: ContractTransaction, expectingRevokedCandidates: string[]) { + await expectEvent( + contractInterface, + 'CandidatesRevoked', + tx, + (event) => { + expect(event.args[0], 'invalid revoked candidates').eql(expectingRevokedCandidates); + }, + 1 + ); + }, + + emitCandidateGrantedEvent: async function ( + tx: ContractTransaction, + expectingConsensusAddr: string, + expectingTreasuryAddr: string, + expectingAdmin: string, + expectingBridgeOperatorAddr: string + ) { + await expectEvent( + contractInterface, + 'CandidateGranted', + tx, + (event) => { + expect(event.args[0], 'invalid consensus address').eql(expectingConsensusAddr); + expect(event.args[1], 'invalid treasury address').eql(expectingTreasuryAddr); + expect(event.args[2], 'invalid admin address').eql(expectingAdmin); + expect(event.args[3], 'invalid bridge operator address').eql(expectingBridgeOperatorAddr); + }, + 1 + ); + }, +}; diff --git a/test/helpers/fixture.ts b/test/helpers/fixture.ts index 9c6243f0a..7e71d3b0f 100644 --- a/test/helpers/fixture.ts +++ b/test/helpers/fixture.ts @@ -49,8 +49,9 @@ export const defaultTestConfig = { minValidatorBalance: BigNumber.from(100), maxValidatorCandidate: 10, - bonusPerBlock: BigNumber.from(1), - topupAmount: BigNumber.from(10000), + validatorBonusPerBlock: BigNumber.from(1), + bridgeOperatorBonusPerBlock: BigNumber.from(1), + topupAmount: BigNumber.from(100000000000), minMaintenanceBlockPeriod: 100, maxMaintenanceBlockPeriod: 1000, minOffset: 200, @@ -105,7 +106,9 @@ export const initTest = (id: string) => minValidatorBalance: options?.minValidatorBalance ?? defaultTestConfig.minValidatorBalance, }; stakingVestingConfig[network.name] = { - bonusPerBlock: options?.bonusPerBlock ?? defaultTestConfig.bonusPerBlock, + validatorBonusPerBlock: options?.validatorBonusPerBlock ?? defaultTestConfig.validatorBonusPerBlock, + bridgeOperatorBonusPerBlock: + options?.bridgeOperatorBonusPerBlock ?? defaultTestConfig.bridgeOperatorBonusPerBlock, topupAmount: options?.topupAmount ?? defaultTestConfig.topupAmount, }; roninTrustedOrganizationConf[network.name] = { diff --git a/test/helpers/ronin-validator-set.ts b/test/helpers/ronin-validator-set.ts index 0061951d7..68df91dcb 100644 --- a/test/helpers/ronin-validator-set.ts +++ b/test/helpers/ronin-validator-set.ts @@ -105,7 +105,7 @@ export const expects = { tx: ContractTransaction, expectingCoinbaseAddr: string, expectingSubmittedReward: BigNumberish, - expectingBonusReward: BigNumberish + expectingStakingVesting: BigNumberish ) { await expectEvent( contractInterface, @@ -114,7 +114,7 @@ export const expects = { (event) => { expect(event.args[0], 'invalid coinbase address').eq(expectingCoinbaseAddr); expect(event.args[1], 'invalid submitted reward').eq(expectingSubmittedReward); - expect(event.args[2], 'invalid bonus reward').eq(expectingBonusReward); + expect(event.args[2], 'invalid staking vesting').eq(expectingStakingVesting); }, 1 ); @@ -123,6 +123,7 @@ export const expects = { emitMiningRewardDistributedEvent: async function ( tx: ContractTransaction, expectingCoinbaseAddr: string, + expectingRecipientAddr: string, expectingAmount: BigNumberish ) { await expectEvent( @@ -131,7 +132,27 @@ export const expects = { tx, (event) => { expect(event.args[0], 'invalid coinbase address').eq(expectingCoinbaseAddr); - expect(event.args[1], 'invalid amount').eq(expectingAmount); + expect(event.args[1], 'invalid recipient address').eq(expectingRecipientAddr); + expect(event.args[2], 'invalid amount').eq(expectingAmount); + }, + 1 + ); + }, + + emitBridgeOperatorRewardDistributedEvent: async function ( + tx: ContractTransaction, + expectingCoinbaseAddr: string, + expectingRecipientAddr: string, + expectingAmount: BigNumberish + ) { + await expectEvent( + contractInterface, + 'BridgeOperatorRewardDistributed', + tx, + (event) => { + expect(event.args[0], 'invalid coinbase address').eq(expectingCoinbaseAddr); + expect(event.args[1], 'invalid recipient address').eq(expectingRecipientAddr); + expect(event.args[2], 'invalid amount').eq(expectingAmount); }, 1 ); @@ -160,4 +181,44 @@ export const expects = { 1 ); }, + + emitBlockProducerSetUpdatedEvent: async function (tx: ContractTransaction, expectingBlockProducers: string[]) { + await expectEvent( + contractInterface, + 'BlockProducerSetUpdated', + tx, + (event) => { + expect(event.args[0], 'invalid validator set').eql(expectingBlockProducers); + }, + 1 + ); + }, + + emitActivatedBlockProducersEvent: async function (tx: ContractTransaction, expectingProducers: string[]) { + await expectEvent( + contractInterface, + 'ActivatedBlockProducers', + tx, + (event) => { + expect(event.args[0], 'invalid activated producer set').eql(expectingProducers); + }, + 1 + ); + }, + + emitDeactivatedBlockProducersEvent: async function (tx: ContractTransaction, expectingProducers: string[]) { + await expectEvent( + contractInterface, + 'DeactivatedBlockProducers', + tx, + (event) => { + expect(event.args[0], 'invalid deactivated producer set').eql(expectingProducers); + }, + 1 + ); + }, + + emitWrappedUpEpochEvent: async function (tx: ContractTransaction) { + await expectEvent(contractInterface, 'WrappedUpEpoch', tx, () => {}, 1); + }, }; diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index 3ecf381ed..f3ed66ea1 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -16,6 +16,7 @@ import { } from '../../src/types'; import { expects as RoninValidatorSetExpects } from '../helpers/ronin-validator-set'; +import { expects as CandidateManagerExpects } from '../helpers/candidate-manager'; import { mineBatchTxs } from '../helpers/utils'; import { SlashType } from '../../src/script/slash-indicator'; import { initTest } from '../helpers/fixture'; @@ -74,6 +75,7 @@ describe('[Integration] Slash validators', () => { describe('Slash one validator', async () => { let expectingValidatorSet: Address[] = []; + let expectingBlockProducerSet: Address[] = []; let period: BigNumberish; before(async () => { @@ -102,7 +104,7 @@ describe('[Integration] Slash validators', () => { }); describe('Slash felony validator -- when the validators balance is sufficient after being slashed', async () => { - let updateValidatorTx: ContractTransaction; + let wrapUpEpochTx: ContractTransaction; let slashValidatorTx: ContractTransaction; let slasheeIdx: number; let slashee: SignerWithAddress; @@ -114,7 +116,7 @@ describe('[Integration] Slash validators', () => { slasheeInitStakingAmount = minValidatorBalance.add(slashFelonyAmount.mul(10)); await stakingContract .connect(slashee) - .applyValidatorCandidate(slashee.address, slashee.address, slashee.address, 2_00, { + .applyValidatorCandidate(slashee.address, slashee.address, slashee.address, slashee.address, 2_00, { value: slasheeInitStakingAmount, }); @@ -122,13 +124,19 @@ describe('[Integration] Slash validators', () => { await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); - updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); + await validatorContract.connect(coinbase).endPeriod(); + wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); + const currentBlock = await ethers.provider.getBlockNumber(); + period = await validatorContract.periodOf(currentBlock); + expectingValidatorSet.push(slashee.address); - await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); + expectingBlockProducerSet.push(slashee.address); + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(wrapUpEpochTx!, expectingValidatorSet); expect(await validatorContract.getValidators()).eql(expectingValidatorSet); + expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); }); it('Should the ValidatorSet contract emit event', async () => { @@ -166,16 +174,18 @@ describe('[Integration] Slash validators', () => { expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(_expectingSlasheeStakingAmount); }); - it('Should the validator set exclude the jailed validator in the next epoch', async () => { + it('Should the block producer set exclude the jailed validator in the next epoch', async () => { await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); - updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); + wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); - expectingValidatorSet.pop(); - await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); + expectingBlockProducerSet.pop(); + expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); + await RoninValidatorSetExpects.emitBlockProducerSetUpdatedEvent(wrapUpEpochTx!, expectingBlockProducerSet); + expect(wrapUpEpochTx).not.emit(validatorContract, 'ValidatorSetUpdated'); }); - it('Should the validator candidate cannot re-join as a validator when jail time is not over', async () => { + it('Should the validator cannot re-join as a block producer when jail time is not over', async () => { let _blockNumber = await network.provider.send('eth_blockNumber'); let _jailUntil = await validatorContract.getJailUntils([slashee.address]); let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber).sub(100); @@ -183,13 +193,13 @@ describe('[Integration] Slash validators', () => { await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); - updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); + wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); - await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, []); + expect(wrapUpEpochTx).not.emit(validatorContract, 'ValidatorSetUpdated'); }); - it('Should the validator candidate re-join as a validator when jail time is over', async () => { + it('Should the validator re-join as a block producer when jail time is over', async () => { let _blockNumber = await network.provider.send('eth_blockNumber'); let _jailUntil = await validatorContract.getJailUntils([slashee.address]); let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber); @@ -197,15 +207,18 @@ describe('[Integration] Slash validators', () => { await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); - updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); + wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); - expectingValidatorSet.push(slashee.address); - await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); + expectingBlockProducerSet.push(slashee.address); + + expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); + await RoninValidatorSetExpects.emitBlockProducerSetUpdatedEvent(wrapUpEpochTx!, expectingBlockProducerSet); + expect(wrapUpEpochTx).not.emit(validatorContract, 'ValidatorSetUpdated'); }); }); describe('Slash felony validator -- when the validators balance is equal to minimum balance', async () => { - let updateValidatorTx: ContractTransaction; + let wrapUpEpochTx: ContractTransaction; let slashValidatorTx: ContractTransaction; let slasheeIdx: number; let slashee: SignerWithAddress; @@ -218,7 +231,7 @@ describe('[Integration] Slash validators', () => { await stakingContract .connect(slashee) - .applyValidatorCandidate(slashee.address, slashee.address, slashee.address, 2_00, { + .applyValidatorCandidate(slashee.address, slashee.address, slashee.address, slashee.address, 2_00, { value: slasheeInitStakingAmount, }); @@ -226,104 +239,157 @@ describe('[Integration] Slash validators', () => { await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); - updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); + await validatorContract.connect(coinbase).endPeriod(); + wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); + + const currentBlock = await ethers.provider.getBlockNumber(); + period = await validatorContract.periodOf(currentBlock); + expectingValidatorSet.push(slashee.address); - await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(wrapUpEpochTx!, expectingValidatorSet); expect(await validatorContract.getValidators()).eql(expectingValidatorSet); }); - it('Should the ValidatorSet contract emit event', async () => { - for (let i = 0; i < felonyThreshold - 1; i++) { - await slashContract.connect(coinbase).slash(slashee.address); - } - slashValidatorTx = await slashContract.connect(coinbase).slash(slashee.address); + describe('Check effects on indicator and staked amount', async () => { + it('Should the ValidatorSet contract emit event', async () => { + for (let i = 0; i < felonyThreshold - 1; i++) { + await slashContract.connect(coinbase).slash(slashee.address); + } + slashValidatorTx = await slashContract.connect(coinbase).slash(slashee.address); - await expect(slashValidatorTx) - .to.emit(slashContract, 'UnavailabilitySlashed') - .withArgs(slashee.address, SlashType.FELONY, period); + await expect(slashValidatorTx) + .to.emit(slashContract, 'UnavailabilitySlashed') + .withArgs(slashee.address, SlashType.FELONY, period); - let blockNumber = await network.provider.send('eth_blockNumber'); + let blockNumber = await network.provider.send('eth_blockNumber'); - await expect(slashValidatorTx) - .to.emit(validatorContract, 'ValidatorPunished') - .withArgs(slashee.address, BigNumber.from(blockNumber).add(felonyJailBlocks), slashFelonyAmount); - }); + await expect(slashValidatorTx) + .to.emit(validatorContract, 'ValidatorPunished') + .withArgs(slashee.address, BigNumber.from(blockNumber).add(felonyJailBlocks), slashFelonyAmount); + }); - it('Should the validator is put in jail', async () => { - let blockNumber = await network.provider.send('eth_blockNumber'); - expect(await validatorContract.getJailUntils([slashee.address])).eql([ - BigNumber.from(blockNumber).add(felonyJailBlocks), - ]); - }); + it('Should the validator is put in jail', async () => { + let blockNumber = await network.provider.send('eth_blockNumber'); + expect(await validatorContract.getJailUntils([slashee.address])).eql([ + BigNumber.from(blockNumber).add(felonyJailBlocks), + ]); + }); - it('Should the Staking contract emit Unstaked event', async () => { - await expect(slashValidatorTx) - .to.emit(stakingContract, 'Unstaked') - .withArgs(slashee.address, slashFelonyAmount); - }); + it('Should the Staking contract emit Unstaked event', async () => { + await expect(slashValidatorTx) + .to.emit(stakingContract, 'Unstaked') + .withArgs(slashee.address, slashFelonyAmount); + }); - it('Should the Staking contract subtract staked amount from validator', async () => { - let _expectingSlasheeStakingAmount = slasheeInitStakingAmount.sub(slashFelonyAmount); - expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(_expectingSlasheeStakingAmount); + it('Should the Staking contract subtract staked amount from validator', async () => { + let _expectingSlasheeStakingAmount = slasheeInitStakingAmount.sub(slashFelonyAmount); + expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(_expectingSlasheeStakingAmount); + }); }); - it('Should the validator set exclude the jailed validator in the next epoch', async () => { - await mineBatchTxs(async () => { - await validatorContract.connect(coinbase).endEpoch(); - updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); + describe('Check effects on validator set and producer set', async () => { + it('Should the block producer set exclude the jailed validator in the next epoch', async () => { + await mineBatchTxs(async () => { + await validatorContract.connect(coinbase).endEpoch(); + wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); + }); + expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); + await RoninValidatorSetExpects.emitBlockProducerSetUpdatedEvent(wrapUpEpochTx!, expectingBlockProducerSet); }); - expectingValidatorSet.pop(); - await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); - }); - it('Should the validator candidate cannot re-join as a validator when jail time is not over', async () => { - let _blockNumber = await network.provider.send('eth_blockNumber'); - let _jailUntil = await validatorContract.getJailUntils([slashee.address]); - let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber).sub(100); + it('Should the validator cannot re-join as a block producer when jail time is not over', async () => { + let _blockNumber = await network.provider.send('eth_blockNumber'); + let _jailUntil = await validatorContract.getJailUntils([slashee.address]); + let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber).sub(100); - await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); - await mineBatchTxs(async () => { - await validatorContract.connect(coinbase).endEpoch(); - updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); + await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); + await mineBatchTxs(async () => { + await validatorContract.connect(coinbase).endEpoch(); + wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); + }); + + expect(wrapUpEpochTx).not.emit(validatorContract, 'ValidatorSetUpdated'); }); - await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); - }); + it('Should the validator can join as a validator when jail time is over, despite of insufficient fund', async () => { + let _blockNumber = await network.provider.send('eth_blockNumber'); + let _jailUntil = await validatorContract.getJailUntils([slashee.address]); + let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber); - it('Should the validator candidate cannot join as a validator when jail time is over, due to insufficient fund', async () => { - let _blockNumber = await network.provider.send('eth_blockNumber'); - let _jailUntil = await validatorContract.getJailUntils([slashee.address]); - let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber); + await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); + await mineBatchTxs(async () => { + await validatorContract.connect(coinbase).endEpoch(); + wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); + }); + expectingBlockProducerSet.push(slashee.address); - await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); - await mineBatchTxs(async () => { - await validatorContract.connect(coinbase).endEpoch(); - updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); + expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); + await RoninValidatorSetExpects.emitBlockProducerSetUpdatedEvent(wrapUpEpochTx!, expectingBlockProducerSet); + expect(wrapUpEpochTx).not.emit(validatorContract, 'ValidatorSetUpdated'); + }); + + it.skip('Should the delegators cannot top-up for the under balance and to-be-kicked validator', async () => { + // TODO: }); - await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); }); - it('Should the validator top-up balance for being sufficient minimum balance of a validator', async () => { - let topUpTx = await stakingContract.connect(slashee).stake(slashee.address, { - value: slashFelonyAmount, + describe('Check effects on candidate list when the under balance candidates get kicked out', async () => { + let expectingRevokedCandidates: Address[]; + it('Should the event of updating validator set emitted', async () => { + await mineBatchTxs(async () => { + await validatorContract.connect(coinbase).endEpoch(); + await validatorContract.connect(coinbase).endPeriod(); + wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); + }); + + expectingRevokedCandidates = expectingValidatorSet.slice(-1); + expectingBlockProducerSet.pop(); + expectingValidatorSet.pop(); + + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(wrapUpEpochTx!, expectingValidatorSet); + + expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); + expect(await validatorContract.getValidators()).eql(expectingValidatorSet); + }); + + it('Should the event of revoking under balance candidates emitted', async () => { + await CandidateManagerExpects.emitCandidatesRevokedEvent(wrapUpEpochTx, expectingRevokedCandidates); }); - await expect(topUpTx).to.emit(stakingContract, 'Staked').withArgs(slashee.address, slashFelonyAmount); + it('Should the isValidatorCandidate method return false on kicked candidates', async () => { + for (let _addr of expectingRevokedCandidates) { + expect(await validatorContract.isValidatorCandidate(_addr)).eq(false); + } + }); }); - // NOTE: the candidate is kicked right after the epoch is ended. - it.skip('Should the validator be able to re-join the validator set', async () => { - await mineBatchTxs(async () => { - await validatorContract.connect(coinbase).endEpoch(); - updateValidatorTx = await validatorContract.connect(coinbase).wrapUpEpoch(); + describe('Kicked candidates re-join', async () => { + it('Should the kicked validators cannot top-up, since they are not candidates anymore', async () => { + let topUpTx = stakingContract.connect(slashee).stake(slashee.address, { + value: slashFelonyAmount, + }); + + await expect(topUpTx).revertedWith('StakingManager: query for non-existent pool'); + }); + + it('Should the kicked validator be able to re-join as a candidate', async () => { + let applyCandidateTx = await stakingContract + .connect(slashee) + .applyValidatorCandidate(slashee.address, slashee.address, slashee.address, slashee.address, 2_00, { + value: slasheeInitStakingAmount, + }); + + await CandidateManagerExpects.emitCandidateGrantedEvent( + applyCandidateTx!, + slashee.address, + slashee.address, + slashee.address, + slashee.address + ); }); - expectingValidatorSet.push(slashee.address); - await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(updateValidatorTx!, expectingValidatorSet); }); }); }); - - // TODO(Bao): Test for reward amount of validators and delegators }); diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index 182685146..bd157fa67 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -33,7 +33,7 @@ const felonyThreshold = 10; const slashFelonyAmount = BigNumber.from(1); const slashDoubleSignAmount = 1000; const minValidatorBalance = BigNumber.from(100); -const bonusPerBlock = BigNumber.from(1); +const validatorBonusPerBlock = BigNumber.from(1); describe('[Integration] Submit Block Reward', () => { const blockRewardAmount = BigNumber.from(2); @@ -46,7 +46,7 @@ describe('[Integration] Submit Block Reward', () => { await initTest('ActionSubmitReward')({ felonyThreshold, minValidatorBalance, - bonusPerBlock, + validatorBonusPerBlock, slashFelonyAmount, slashDoubleSignAmount, trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: 100 })), @@ -93,11 +93,12 @@ describe('[Integration] Submit Block Reward', () => { validator = validatorCandidates[0]; await stakingContract .connect(validator) - .applyValidatorCandidate(validator.address, validator.address, validator.address, 2_00, { + .applyValidatorCandidate(validator.address, validator.address, validator.address, validator.address, 2_00, { value: initStakingAmount, }); await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); + await validatorContract.connect(coinbase).endPeriod(); await validatorContract.connect(coinbase).wrapUpEpoch(); }); }); @@ -118,7 +119,7 @@ describe('[Integration] Submit Block Reward', () => { it('Should the ValidatorSetContract emit event of submitting reward', async () => { await expect(submitRewardTx) .to.emit(validatorContract, 'BlockRewardSubmitted') - .withArgs(validator.address, blockRewardAmount, bonusPerBlock); + .withArgs(validator.address, blockRewardAmount, validatorBonusPerBlock); }); it.skip('Should the ValidatorSetContract update mining reward', async () => {}); @@ -140,7 +141,7 @@ describe('[Integration] Submit Block Reward', () => { await stakingContract .connect(validator) - .applyValidatorCandidate(validator.address, validator.address, validator.address, 2_00, { + .applyValidatorCandidate(validator.address, validator.address, validator.address, validator.address, 2_00, { value: initStakingAmount, }); diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index cd6f08842..fac5ebbc4 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -18,6 +18,7 @@ import { expects as ValidatorSetExpects } from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../helpers/utils'; import { initTest } from '../helpers/fixture'; import { GovernanceAdminInterface } from '../../src/script/governance-admin-interface'; +import { Address } from 'hardhat-deploy/dist/types'; let slashContract: SlashIndicator; let stakingContract: Staking; @@ -109,6 +110,7 @@ describe('[Integration] Wrap up epoch', () => { validatorCandidates[i].address, validatorCandidates[i].address, validatorCandidates[i].address, + validatorCandidates[i].address, 2_00, { value: minValidatorBalance.mul(2).add(i), @@ -118,6 +120,7 @@ describe('[Integration] Wrap up epoch', () => { await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); + await validatorContract.connect(coinbase).endPeriod(); await validatorContract.connect(coinbase).wrapUpEpoch(); }); @@ -204,13 +207,21 @@ describe('[Integration] Wrap up epoch', () => { for (let i = 0; i < validators.length; i++) { await stakingContract .connect(validators[i]) - .applyValidatorCandidate(validators[i].address, validators[i].address, validators[i].address, 2_00, { - value: minValidatorBalance.mul(3).add(i), - }); + .applyValidatorCandidate( + validators[i].address, + validators[i].address, + validators[i].address, + validators[i].address, + 2_00, + { + value: minValidatorBalance.mul(3).add(i), + } + ); } await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); + await validatorContract.connect(coinbase).endPeriod(); await validatorContract.connect(coinbase).wrapUpEpoch(); }); @@ -223,27 +234,35 @@ describe('[Integration] Wrap up epoch', () => { }); describe('One validator get slashed between period', async () => { + let slasheeAddress: Address; + before(async () => { + slasheeAddress = validators[1].address; await mineBatchTxs(async () => { await validatorContract.endEpoch(); await validatorContract.wrapUpEpoch(); }); for (let i = 0; i < felonyThreshold; i++) { - await slashContract.connect(coinbase).slash(validators[1].address); + await slashContract.connect(coinbase).slash(slasheeAddress); } }); - it('Should the validator set get updated (excluding the slashed validator)', async () => { + it('Should the block producer set get updated (excluding the slashed validator)', async () => { await mineBatchTxs(async () => { await validatorContract.endEpoch(); wrapUpTx = await validatorContract.wrapUpEpoch(); }); - await ValidatorSetExpects.emitValidatorSetUpdatedEvent( - wrapUpTx, - [validators[0], validators[2], coinbase].map((_) => _.address).reverse() + let expectingBlockProducerSet = [validators[2], validators[3]].map((_) => _.address).reverse(); + + await ValidatorSetExpects.emitWrappedUpEpochEvent(wrapUpTx!); + await ValidatorSetExpects.emitBlockProducerSetUpdatedEvent(wrapUpTx!, expectingBlockProducerSet); + + expect(await validatorContract.getValidators()).eql( + [validators[1], validators[2], validators[3]].map((_) => _.address).reverse() ); + expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); }); it('Should the validators in the previous epoch (including slashed one) got slashing counter reset, when the epoch ends', async () => { diff --git a/test/integration/Configuration.test.ts b/test/integration/Configuration.test.ts index c1cfe34ac..148943117 100644 --- a/test/integration/Configuration.test.ts +++ b/test/integration/Configuration.test.ts @@ -42,7 +42,7 @@ const numberOfEpochsInPeriod = 48; const minValidatorBalance = BigNumber.from(100); const maxValidatorCandidate = 10; -const bonusPerBlock = BigNumber.from(1); +const validatorBonusPerBlock = BigNumber.from(1); const topupAmount = BigNumber.from(10000); const minMaintenanceBlockPeriod = 100; const maxMaintenanceBlockPeriod = 1000; @@ -70,7 +70,7 @@ describe('[Integration] Configuration check', () => { numberOfEpochsInPeriod, minValidatorBalance, maxValidatorCandidate, - bonusPerBlock, + validatorBonusPerBlock, topupAmount, minMaintenanceBlockPeriod, maxMaintenanceBlockPeriod, @@ -105,8 +105,10 @@ describe('[Integration] Configuration check', () => { }); it('Should the StakingVestingContract config the block bonus correctly', async () => { - expect(await stakingVestingContract.blockBonus(0)).eq(bonusPerBlock); - expect(await stakingVestingContract.blockBonus(Math.floor(Math.random() * 1_000_000))).eq(bonusPerBlock); + expect(await stakingVestingContract.validatorBlockBonus(0)).eq(validatorBonusPerBlock); + expect(await stakingVestingContract.validatorBlockBonus(Math.floor(Math.random() * 1_000_000))).eq( + validatorBonusPerBlock + ); }); }); diff --git a/test/maintainance/Maintenance.test.ts b/test/maintainance/Maintenance.test.ts index c9c5df1ff..414bef497 100644 --- a/test/maintainance/Maintenance.test.ts +++ b/test/maintainance/Maintenance.test.ts @@ -11,12 +11,12 @@ import { SlashIndicator__factory, Staking, Staking__factory, - MockRoninValidatorSetSorting__factory, + MockRoninValidatorSetOverridePrecompile__factory, RoninGovernanceAdmin, RoninGovernanceAdmin__factory, } from '../../src/types'; import { initTest } from '../helpers/fixture'; -import { EpochController } from '../helpers/ronin-validator-set'; +import { EpochController, expects as ValidatorSetExpects } from '../helpers/ronin-validator-set'; import { GovernanceAdminInterface } from '../../src/script/governance-admin-interface'; let coinbase: SignerWithAddress; @@ -64,11 +64,11 @@ describe('Maintenance test', () => { maintenanceContract = Maintenance__factory.connect(maintenanceContractAddress, deployer); slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); - validatorContract = MockRoninValidatorSetSorting__factory.connect(validatorContractAddress, deployer); + validatorContract = MockRoninValidatorSetOverridePrecompile__factory.connect(validatorContractAddress, deployer); governanceAdmin = RoninGovernanceAdmin__factory.connect(roninGovernanceAdminAddress, deployer); governanceAdminInterface = new GovernanceAdminInterface(governanceAdmin, governor); - const mockValidatorLogic = await new MockRoninValidatorSetSorting__factory(deployer).deploy(); + const mockValidatorLogic = await new MockRoninValidatorSetOverridePrecompile__factory(deployer).deploy(); await mockValidatorLogic.deployed(); await governanceAdminInterface.upgrade(validatorContract.address, mockValidatorLogic.address); @@ -80,6 +80,7 @@ describe('Maintenance test', () => { validatorCandidates[i].address, validatorCandidates[i].address, validatorCandidates[i].address, + validatorCandidates[i].address, 1, { value: minValidatorBalance.add(maxValidatorNumber).sub(i) } ); @@ -92,10 +93,15 @@ describe('Maintenance test', () => { localEpochController = new EpochController(minOffset, numberOfBlocksInEpoch, numberOfEpochsInPeriod); - await localEpochController.mineToBeforeEndOfEpoch(); + await localEpochController.mineToBeforeEndOfPeriod(); + let tx = await validatorContract.connect(coinbase).wrapUpEpoch(); + await ValidatorSetExpects.emitValidatorSetUpdatedEvent( + tx, + validatorCandidates.map((_) => _.address) + ); - await validatorContract.connect(coinbase).wrapUpEpoch(); expect(await validatorContract.getValidators()).eql(validatorCandidates.map((_) => _.address)); + expect(await validatorContract.getBlockProducers()).eql(validatorCandidates.map((_) => _.address)); }); after(async () => { @@ -201,7 +207,7 @@ describe('Maintenance test', () => { it('Should not be able to schedule maintenance for non-validator address', async () => { await expect(maintenanceContract.connect(validatorCandidates[0]).schedule(deployer.address, 0, 100)).revertedWith( - 'Maintenance: consensus address must be a validator' + 'Maintenance: consensus address must be a block producer' ); }); @@ -229,7 +235,7 @@ describe('Maintenance test', () => { ).revertedWith('Maintenance: already scheduled'); }); - it('Should be able to schedule maintenance using another validator admin account', async () => { + it('Should be able to schedule maintenance for another validator using their admin account', async () => { await maintenanceContract .connect(validatorCandidates[1]) .schedule(validatorCandidates[1].address, startedAtBlock, endedAtBlock); @@ -243,16 +249,18 @@ describe('Maintenance test', () => { ).revertedWith('Maintenance: exceeds total of schedules'); }); - it('Should the validator still appear in the validator list since it is not maintenance time yet', async () => { + it('Should the validator still appear in the block producer list since it is not maintenance time yet', async () => { await localEpochController.mineToBeforeEndOfEpoch(); - await validatorContract.connect(coinbase).wrapUpEpoch(); - expect(await validatorContract.getValidators()).eql(validatorCandidates.map((_) => _.address)); + let tx = await validatorContract.connect(coinbase).wrapUpEpoch(); + expect(await validatorContract.getBlockProducers()).eql(validatorCandidates.map((_) => _.address)); }); - it('Should the validator not appear in the validator list since the maintenance is started', async () => { + it('Should the validator not appear in the block producer list since the maintenance is started', async () => { await localEpochController.mineToBeforeEndOfEpoch(); - await validatorContract.connect(coinbase).wrapUpEpoch(); - expect(await validatorContract.getValidators()).eql(validatorCandidates.slice(2).map((_) => _.address)); + let tx = await validatorContract.connect(coinbase).wrapUpEpoch(); + let expectingBlockProducerSet = validatorCandidates.slice(2).map((_) => _.address); + await ValidatorSetExpects.emitBlockProducerSetUpdatedEvent(tx!, expectingBlockProducerSet); + expect(await validatorContract.getBlockProducers()).eql(validatorCandidates.slice(2).map((_) => _.address)); }); it('[Slash Integration] Should not be able to slash the validator in maintenance time', async () => { @@ -275,10 +283,12 @@ describe('Maintenance test', () => { ]); }); - it('Should the validator appear in the validator list since the maintenance time is ended', async () => { + it('Should the validator appear in the block producer list since the maintenance time is ended', async () => { await localEpochController.mineToBeforeEndOfEpoch(); - await validatorContract.connect(coinbase).wrapUpEpoch(); - expect(await validatorContract.getValidators()).eql(validatorCandidates.map((_) => _.address)); + let tx = await validatorContract.connect(coinbase).wrapUpEpoch(); + let expectingBlockProducerSet = validatorCandidates.map((_) => _.address); + await ValidatorSetExpects.emitBlockProducerSetUpdatedEvent(tx!, expectingBlockProducerSet); + expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); }); it('Should not be able to schedule maintenance twice in a period', async () => { diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index b9879ee4e..a35ba1c1d 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -4,7 +4,7 @@ import { ethers, network } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { - MockRoninValidatorSetSorting__factory, + MockRoninValidatorSetOverridePrecompile__factory, MockSlashIndicatorExtended, MockSlashIndicatorExtended__factory, RoninGovernanceAdmin, @@ -86,12 +86,12 @@ describe('Slash indicator test', () => { }); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); - validatorContract = MockRoninValidatorSetSorting__factory.connect(validatorContractAddress, deployer); + validatorContract = MockRoninValidatorSetOverridePrecompile__factory.connect(validatorContractAddress, deployer); slashContract = MockSlashIndicatorExtended__factory.connect(slashContractAddress, deployer); governanceAdmin = RoninGovernanceAdmin__factory.connect(roninGovernanceAdminAddress, deployer); governanceAdminInterface = new GovernanceAdminInterface(governanceAdmin, governor); - const mockValidatorLogic = await new MockRoninValidatorSetSorting__factory(deployer).deploy(); + const mockValidatorLogic = await new MockRoninValidatorSetOverridePrecompile__factory(deployer).deploy(); await mockValidatorLogic.deployed(); await governanceAdminInterface.upgrade(validatorContract.address, mockValidatorLogic.address); @@ -107,6 +107,7 @@ describe('Slash indicator test', () => { validatorCandidates[i].address, validatorCandidates[i].address, validatorCandidates[i].address, + validatorCandidates[i].address, 1, { value: minValidatorBalance.mul(2).add(maxValidatorNumber).sub(i) } ); @@ -118,7 +119,7 @@ describe('Slash indicator test', () => { ]); localEpochController = new EpochController(minOffset, numberOfBlocksInEpoch, numberOfEpochsInPeriod); - await localEpochController.mineToBeforeEndOfEpoch(); + await localEpochController.mineToBeforeEndOfPeriod(); await validatorContract.connect(coinbase).wrapUpEpoch(); expect(await validatorContract.getValidators()).eql(validatorCandidates.map((_) => _.address)); diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index a7924f96c..79513e507 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -54,7 +54,7 @@ describe('Staking test', () => { describe('Validator candidate test', () => { it('Should not be able to propose validator with insufficient amount', async () => { await expect( - stakingContract.applyValidatorCandidate(userA.address, userA.address, userA.address, 1) + stakingContract.applyValidatorCandidate(userA.address, userA.address, userA.address, userA.address, 1) ).revertedWith('StakingManager: insufficient amount'); }); @@ -67,6 +67,7 @@ describe('Staking test', () => { candidate.address, candidate.address, candidate.address, + candidate.address, 1, /* 0.01% */ { value: minValidatorBalance.mul(2) } ); @@ -81,7 +82,7 @@ describe('Staking test', () => { await expect( stakingContract .connect(poolAddr) - .applyValidatorCandidate(poolAddr.address, poolAddr.address, poolAddr.address, 0, { + .applyValidatorCandidate(poolAddr.address, poolAddr.address, poolAddr.address, poolAddr.address, 0, { value: minValidatorBalance, }) ).revertedWith('CandidateManager: query for already existent candidate'); diff --git a/test/validator/ArrangeValidators.test.ts b/test/validator/ArrangeValidators.test.ts index 174c5f5ff..d497ea48b 100644 --- a/test/validator/ArrangeValidators.test.ts +++ b/test/validator/ArrangeValidators.test.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { BigNumber } from 'ethers'; +import { BigNumber, BigNumberish } from 'ethers'; import { ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { Address } from 'hardhat-deploy/dist/types'; @@ -25,44 +25,45 @@ let governanceAdminInterface: GovernanceAdminInterface; let deployer: SignerWithAddress; let governor: SignerWithAddress; -let validatorCandidates: SignerWithAddress[]; +let candidates: Address[]; const slashFelonyAmount = 100; const maxValidatorNumber = 7; const maxPrioritizedValidatorNumber = 4; const maxValidatorCandidate = 100; +const defaultTrustedWeight = BigNumber.from(100); -const setPriorityStatus = async (addrs: Address[], statuses: boolean[]) => { +const setPriorityStatus = async (addrs: Address[], statuses: boolean[]): Promise => { const arr = statuses.map((stt, i) => ({ address: addrs[i], stt })); const addingTrustedOrgs = arr.filter(({ stt }) => stt).map(({ address }) => address); const removingTrustedOrgs = arr.filter(({ stt }) => !stt).map(({ address }) => address); await governanceAdminInterface.functionDelegateCall( roninTrustedOrganization.address, roninTrustedOrganization.interface.encodeFunctionData('addTrustedOrganizations', [ - addingTrustedOrgs.map((v) => ({ addr: v, weight: 100 })), + addingTrustedOrgs.map((v) => ({ addr: v, weight: defaultTrustedWeight })), ]) ); await governanceAdminInterface.functionDelegateCall( roninTrustedOrganization.address, roninTrustedOrganization.interface.encodeFunctionData('removeTrustedOrganizations', [removingTrustedOrgs]) ); + + return statuses.map((stt) => (stt ? defaultTrustedWeight : 0)); }; -const setPriorityStatusForMany = async (validators: SignerWithAddress[], status: boolean) => { +const setPriorityStatusForMany = async (validators: Address[], status: boolean): Promise => { if (validators.length == 0) { - return; + return []; } - let addrs = validators.map((_) => _.address); let statuses = new Array(validators.length).fill(status); - await setPriorityStatus(addrs, statuses); + return await setPriorityStatus(validators, statuses); }; -const setPriorityStatusByIndexes = async (indexes: number[], statuses: boolean[]) => { +const setPriorityStatusByIndexes = async (indexes: number[], statuses: boolean[]): Promise => { expect(indexes.length, 'invalid input for setPriorityStatusByIndex').eq(statuses.length); - let addrs = indexes.filter((_, j) => statuses[j]).map((i) => validatorCandidates[i].address); - statuses = statuses.filter((s) => s == true); + let addrs = indexes.map((i) => candidates[i]); - await setPriorityStatus(addrs, statuses); + return await setPriorityStatus(addrs, statuses); }; const sortArrayByBoolean = (indexes: number[], statuses: boolean[]) => { @@ -75,7 +76,10 @@ const sortArrayByBoolean = (indexes: number[], statuses: boolean[]) => { describe('Arrange validators', () => { before(async () => { - [deployer, governor, ...validatorCandidates] = await ethers.getSigners(); + [deployer, governor] = await ethers.getSigners(); + candidates = Array.from({ length: maxValidatorCandidate }, (_, i) => + ethers.utils.hexZeroPad(ethers.utils.hexlify(i + 0x40), 20) + ); const { slashContractAddress, @@ -87,7 +91,7 @@ describe('Arrange validators', () => { maxValidatorCandidate, maxPrioritizedValidatorNumber, slashFelonyAmount, - trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: 100 })), + trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: defaultTrustedWeight })), }); validatorContract = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); @@ -107,44 +111,44 @@ describe('Arrange validators', () => { describe('Update priority list', async () => { it('Should be able to add new prioritized validators', async () => { - let addrs = validatorCandidates.slice(0, 10).map((_) => _.address); + let addrs = candidates.slice(0, 10); let statuses = new Array(10).fill(true); await setPriorityStatus(addrs, statuses); }); it('Should be able to remove prioritized validators', async () => { - let addrs = validatorCandidates.slice(0, 10).map((_) => _.address); + let addrs = candidates.slice(0, 10); let statuses = new Array(10).fill(false); await setPriorityStatus(addrs, statuses); }); it('Should be able to add and remove prioritized validators: num(add) > num(remove)', async () => { - let addrs = validatorCandidates.slice(0, 10).map((_) => _.address); + let addrs = candidates.slice(0, 10); let statuses = new Array(10).fill(true); await setPriorityStatus(addrs, statuses); - addrs = validatorCandidates.slice(4, 7).map((_) => _.address); + addrs = candidates.slice(4, 7); statuses = new Array(3).fill(false); - addrs.push(...validatorCandidates.slice(10, 15).map((_) => _.address)); + addrs.push(...candidates.slice(10, 15)); statuses.push(...new Array(5).fill(true)); await setPriorityStatus(addrs, statuses); }); it('Should be able to add and remove prioritized validators: num(add) < num(remove)', async () => { - let addrs = validatorCandidates.slice(0, 15).map((_) => _.address); + let addrs = candidates.slice(0, 15); let statuses = new Array(15).fill(false); await setPriorityStatus(addrs, statuses); - addrs = validatorCandidates.slice(0, 10).map((_) => _.address); + addrs = candidates.slice(0, 10); statuses = new Array(10).fill(true); await setPriorityStatus(addrs, statuses); - addrs = validatorCandidates.slice(1, 8).map((_) => _.address); + addrs = candidates.slice(1, 8); statuses = new Array(7).fill(false); - addrs.push(...validatorCandidates.slice(10, 14).map((_) => _.address)); + addrs.push(...candidates.slice(10, 14)); statuses.push(...new Array(4).fill(true)); await setPriorityStatus(addrs, statuses); }); @@ -154,7 +158,7 @@ describe('Arrange validators', () => { const maxRegularValidatorNumber = maxValidatorNumber - maxPrioritizedValidatorNumber; beforeEach(async () => { - let validators = validatorCandidates.slice(0, 15); + let validators = candidates.slice(0, 15); await setPriorityStatusForMany(validators, false); }); @@ -162,24 +166,24 @@ describe('Arrange validators', () => { let actualPrioritizedNumber = maxPrioritizedValidatorNumber; let actualRegularNumber = maxRegularValidatorNumber; - let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); - let regularValidators = validatorCandidates.slice( - actualPrioritizedNumber, - actualPrioritizedNumber + actualRegularNumber - ); + let prioritizedValidators = candidates.slice(0, actualPrioritizedNumber); + let regularValidators = candidates.slice(actualPrioritizedNumber, actualPrioritizedNumber + actualRegularNumber); - await setPriorityStatusForMany(prioritizedValidators, true); - await setPriorityStatusForMany(regularValidators, false); + let prioritizedWeights = await setPriorityStatusForMany(prioritizedValidators, true); + let regularWeights = await setPriorityStatusForMany(regularValidators, false); - let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators]; + let inputTrustedWeights = [...regularWeights, ...prioritizedWeights]; let expectingValidatorAddrs = [ ...prioritizedValidators.slice(0, maxPrioritizedValidatorNumber), ...regularValidators.slice(0, maxRegularValidatorNumber), - ].map((_) => _.address); + ]; let outputValidators = await validatorContract.arrangeValidatorCandidates( inputValidatorAddrs, - actualPrioritizedNumber + actualRegularNumber + inputTrustedWeights, + actualPrioritizedNumber + actualRegularNumber, + maxPrioritizedValidatorNumber ); expect(outputValidators).eql(expectingValidatorAddrs); @@ -189,24 +193,24 @@ describe('Arrange validators', () => { let actualPrioritizedNumber = maxPrioritizedValidatorNumber; let actualRegularNumber = maxRegularValidatorNumber + 10; - let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); - let regularValidators = validatorCandidates.slice( - actualPrioritizedNumber, - actualPrioritizedNumber + actualRegularNumber - ); + let prioritizedValidators = candidates.slice(0, actualPrioritizedNumber); + let regularValidators = candidates.slice(actualPrioritizedNumber, actualPrioritizedNumber + actualRegularNumber); - await setPriorityStatusForMany(prioritizedValidators, true); - await setPriorityStatusForMany(regularValidators, false); + let prioritizedWeights = await setPriorityStatusForMany(prioritizedValidators, true); + let regularWeights = await setPriorityStatusForMany(regularValidators, false); - let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators]; + let inputTrustedWeights = [...regularWeights, ...prioritizedWeights]; let expectingValidatorAddrs = [ ...prioritizedValidators.slice(0, maxPrioritizedValidatorNumber), ...regularValidators.slice(0, maxRegularValidatorNumber), - ].map((_) => _.address); + ]; let outputValidators = await validatorContract.arrangeValidatorCandidates( inputValidatorAddrs, - maxValidatorNumber + inputTrustedWeights, + maxValidatorNumber, + maxPrioritizedValidatorNumber ); expect(outputValidators).eql(expectingValidatorAddrs); }); @@ -215,24 +219,24 @@ describe('Arrange validators', () => { let actualPrioritizedNumber = maxPrioritizedValidatorNumber; let actualRegularNumber = maxRegularValidatorNumber - 1; - let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); - let regularValidators = validatorCandidates.slice( - actualPrioritizedNumber, - actualPrioritizedNumber + actualRegularNumber - ); + let prioritizedValidators = candidates.slice(0, actualPrioritizedNumber); + let regularValidators = candidates.slice(actualPrioritizedNumber, actualPrioritizedNumber + actualRegularNumber); - await setPriorityStatusForMany(prioritizedValidators, true); - await setPriorityStatusForMany(regularValidators, false); + let prioritizedWeights = await setPriorityStatusForMany(prioritizedValidators, true); + let regularWeights = await setPriorityStatusForMany(regularValidators, false); - let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators]; + let inputTrustedWeights = [...regularWeights, ...prioritizedWeights]; let expectingValidatorAddrs = [ ...prioritizedValidators.slice(0, maxPrioritizedValidatorNumber), ...regularValidators.slice(0, actualRegularNumber), - ].map((_) => _.address); + ]; let outputValidators = await validatorContract.arrangeValidatorCandidates( inputValidatorAddrs, - actualPrioritizedNumber + actualRegularNumber + inputTrustedWeights, + actualPrioritizedNumber + actualRegularNumber, + maxPrioritizedValidatorNumber ); expect(outputValidators).eql(expectingValidatorAddrs); }); @@ -241,24 +245,24 @@ describe('Arrange validators', () => { let actualPrioritizedNumber = maxPrioritizedValidatorNumber + 10; let actualRegularNumber = maxRegularValidatorNumber; - let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); - let regularValidators = validatorCandidates.slice( - actualPrioritizedNumber, - actualPrioritizedNumber + actualRegularNumber - ); + let prioritizedValidators = candidates.slice(0, actualPrioritizedNumber); + let regularValidators = candidates.slice(actualPrioritizedNumber, actualPrioritizedNumber + actualRegularNumber); - await setPriorityStatusForMany(prioritizedValidators, true); - await setPriorityStatusForMany(regularValidators, false); + let prioritizedWeights = await setPriorityStatusForMany(prioritizedValidators, true); + let regularWeights = await setPriorityStatusForMany(regularValidators, false); - let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators]; + let inputTrustedWeights = [...regularWeights, ...prioritizedWeights]; let expectingValidatorAddrs = [ ...prioritizedValidators.slice(0, maxPrioritizedValidatorNumber), ...regularValidators.slice(0, maxRegularValidatorNumber), - ].map((_) => _.address); + ]; let outputValidators = await validatorContract.arrangeValidatorCandidates( inputValidatorAddrs, - maxValidatorNumber + inputTrustedWeights, + maxValidatorNumber, + maxPrioritizedValidatorNumber ); expect(outputValidators).eql(expectingValidatorAddrs); }); @@ -267,24 +271,24 @@ describe('Arrange validators', () => { let actualPrioritizedNumber = maxPrioritizedValidatorNumber + 10; let actualRegularNumber = maxRegularValidatorNumber + 10; - let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); - let regularValidators = validatorCandidates.slice( - actualPrioritizedNumber, - actualPrioritizedNumber + actualRegularNumber - ); + let prioritizedValidators = candidates.slice(0, actualPrioritizedNumber); + let regularValidators = candidates.slice(actualPrioritizedNumber, actualPrioritizedNumber + actualRegularNumber); - await setPriorityStatusForMany(prioritizedValidators, true); - await setPriorityStatusForMany(regularValidators, false); + let prioritizedWeights = await setPriorityStatusForMany(prioritizedValidators, true); + let regularWeights = await setPriorityStatusForMany(regularValidators, false); - let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators]; + let inputTrustedWeights = [...regularWeights, ...prioritizedWeights]; let expectingValidatorAddrs = [ ...prioritizedValidators.slice(0, maxPrioritizedValidatorNumber), ...regularValidators.slice(0, maxRegularValidatorNumber), - ].map((_) => _.address); + ]; let outputValidators = await validatorContract.arrangeValidatorCandidates( inputValidatorAddrs, - maxValidatorNumber + inputTrustedWeights, + maxValidatorNumber, + maxPrioritizedValidatorNumber ); expect(outputValidators).eql(expectingValidatorAddrs); }); @@ -294,25 +298,25 @@ describe('Arrange validators', () => { let actualPrioritizedNumber = maxPrioritizedValidatorNumber + 10; let actualRegularNumber = maxRegularValidatorNumber - spareSlotNumber; - let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); - let regularValidators = validatorCandidates.slice( - actualPrioritizedNumber, - actualPrioritizedNumber + actualRegularNumber - ); + let prioritizedValidators = candidates.slice(0, actualPrioritizedNumber); + let regularValidators = candidates.slice(actualPrioritizedNumber, actualPrioritizedNumber + actualRegularNumber); - await setPriorityStatusForMany(prioritizedValidators, true); - await setPriorityStatusForMany(regularValidators, false); + let prioritizedWeights = await setPriorityStatusForMany(prioritizedValidators, true); + let regularWeights = await setPriorityStatusForMany(regularValidators, false); - let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators]; + let inputTrustedWeights = [...regularWeights, ...prioritizedWeights]; let expectingValidatorAddrs = [ ...prioritizedValidators.slice(0, maxPrioritizedValidatorNumber), ...regularValidators.slice(0, maxRegularValidatorNumber), ...prioritizedValidators.slice(maxPrioritizedValidatorNumber, maxPrioritizedValidatorNumber + spareSlotNumber), - ].map((_) => _.address); + ]; let outputValidators = await validatorContract.arrangeValidatorCandidates( inputValidatorAddrs, - maxValidatorNumber + inputTrustedWeights, + maxValidatorNumber, + maxPrioritizedValidatorNumber ); expect(outputValidators).eql(expectingValidatorAddrs); }); @@ -321,24 +325,24 @@ describe('Arrange validators', () => { let actualPrioritizedNumber = maxPrioritizedValidatorNumber - 1; let actualRegularNumber = maxRegularValidatorNumber; - let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); - let regularValidators = validatorCandidates.slice( - actualPrioritizedNumber, - actualPrioritizedNumber + actualRegularNumber - ); + let prioritizedValidators = candidates.slice(0, actualPrioritizedNumber); + let regularValidators = candidates.slice(actualPrioritizedNumber, actualPrioritizedNumber + actualRegularNumber); - await setPriorityStatusForMany(prioritizedValidators, true); - await setPriorityStatusForMany(regularValidators, false); + let prioritizedWeights = await setPriorityStatusForMany(prioritizedValidators, true); + let regularWeights = await setPriorityStatusForMany(regularValidators, false); - let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators]; + let inputTrustedWeights = [...regularWeights, ...prioritizedWeights]; let expectingValidatorAddrs = [ ...prioritizedValidators.slice(0, actualPrioritizedNumber), ...regularValidators.slice(0, maxRegularValidatorNumber), - ].map((_) => _.address); + ]; let outputValidators = await validatorContract.arrangeValidatorCandidates( inputValidatorAddrs, - actualPrioritizedNumber + actualRegularNumber + inputTrustedWeights, + actualPrioritizedNumber + actualRegularNumber, + maxPrioritizedValidatorNumber ); expect(outputValidators).eql(expectingValidatorAddrs); }); @@ -348,25 +352,25 @@ describe('Arrange validators', () => { let actualPrioritizedNumber = maxPrioritizedValidatorNumber - spareSlotNumber; let actualRegularNumber = maxRegularValidatorNumber + 10; - let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); - let regularValidators = validatorCandidates.slice( - actualPrioritizedNumber, - actualPrioritizedNumber + actualRegularNumber - ); + let prioritizedValidators = candidates.slice(0, actualPrioritizedNumber); + let regularValidators = candidates.slice(actualPrioritizedNumber, actualPrioritizedNumber + actualRegularNumber); - await setPriorityStatusForMany(prioritizedValidators, true); - await setPriorityStatusForMany(regularValidators, false); + let prioritizedWeights = await setPriorityStatusForMany(prioritizedValidators, true); + let regularWeights = await setPriorityStatusForMany(regularValidators, false); - let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators]; + let inputTrustedWeights = [...regularWeights, ...prioritizedWeights]; let expectingValidatorAddrs = [ ...prioritizedValidators.slice(0, maxPrioritizedValidatorNumber), ...regularValidators.slice(0, maxRegularValidatorNumber), ...regularValidators.slice(maxRegularValidatorNumber, maxRegularValidatorNumber + spareSlotNumber), - ].map((_) => _.address); + ]; let outputValidators = await validatorContract.arrangeValidatorCandidates( inputValidatorAddrs, - maxValidatorNumber + inputTrustedWeights, + maxValidatorNumber, + maxPrioritizedValidatorNumber ); expect(outputValidators).eql(expectingValidatorAddrs); }); @@ -375,24 +379,24 @@ describe('Arrange validators', () => { let actualPrioritizedNumber = maxPrioritizedValidatorNumber - 2; let actualRegularNumber = maxRegularValidatorNumber - 2; - let prioritizedValidators = validatorCandidates.slice(0, actualPrioritizedNumber); - let regularValidators = validatorCandidates.slice( - actualPrioritizedNumber, - actualPrioritizedNumber + actualRegularNumber - ); + let prioritizedValidators = candidates.slice(0, actualPrioritizedNumber); + let regularValidators = candidates.slice(actualPrioritizedNumber, actualPrioritizedNumber + actualRegularNumber); - await setPriorityStatusForMany(prioritizedValidators, true); - await setPriorityStatusForMany(regularValidators, false); + let prioritizedWeights = await setPriorityStatusForMany(prioritizedValidators, true); + let regularWeights = await setPriorityStatusForMany(regularValidators, false); - let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators].map((_) => _.address); + let inputValidatorAddrs = [...regularValidators, ...prioritizedValidators]; + let inputTrustedWeights = [...regularWeights, ...prioritizedWeights]; let expectingValidatorAddrs = [ ...prioritizedValidators.slice(0, actualPrioritizedNumber), ...regularValidators.slice(0, actualRegularNumber), - ].map((_) => _.address); + ]; let outputValidators = await validatorContract.arrangeValidatorCandidates( inputValidatorAddrs, - actualPrioritizedNumber + actualRegularNumber + inputTrustedWeights, + actualPrioritizedNumber + actualRegularNumber, + maxPrioritizedValidatorNumber ); expect(outputValidators).eql(expectingValidatorAddrs); }); @@ -400,7 +404,7 @@ describe('Arrange validators', () => { describe('Arrange shuffled validators test', async () => { beforeEach(async () => { - let validators = validatorCandidates.slice(0, 15); + let validators = candidates.slice(0, 15); await setPriorityStatusForMany(validators, false); }); @@ -409,16 +413,18 @@ describe('Arrange validators', () => { let indexes = [0, 1, 2, 3, 4, 5, 6]; let statuses = [true, false, true, true, false, true, false]; - await setPriorityStatusByIndexes(indexes, statuses); + let inputTrustedWeights = await setPriorityStatusByIndexes(indexes, statuses); - let sortedIndexes = sortArrayByBoolean(indexes, statuses); - let expectingValidatorAddrs = sortedIndexes.map((i) => validatorCandidates[i].address); + let sortedIndexes = sortArrayByBoolean([...indexes], statuses); + let expectingValidatorAddrs = sortedIndexes.map((i) => candidates[i]); + + let inputValidatorAddrs = indexes.map((i) => candidates[i]); - let inputValidatorAddrs = indexes.map((i) => validatorCandidates[i].address); let outputValidators = await validatorContract.arrangeValidatorCandidates( inputValidatorAddrs, - - maxValidatorNumber + inputTrustedWeights, + maxValidatorNumber, + maxPrioritizedValidatorNumber ); expect(outputValidators).eql(expectingValidatorAddrs); }); @@ -428,36 +434,37 @@ describe('Arrange validators', () => { let indexes = [0, 1, 2, 3, 4, 5, 6]; let statuses = [true, false, true, true, false, true, true]; - await setPriorityStatusByIndexes(indexes, statuses); + let inputTrustedWeights = await setPriorityStatusByIndexes(indexes, statuses); let sortedIndexes = [0, 2, 3, 5, 1, 4, 6]; - let expectingValidatorAddrs = sortedIndexes.map((i) => validatorCandidates[i].address); + let expectingValidatorAddrs = sortedIndexes.map((i) => candidates[i]); - let inputValidatorAddrs = indexes.map((i) => validatorCandidates[i].address); + let inputValidatorAddrs = indexes.map((i) => candidates[i]); let outputValidators = await validatorContract.arrangeValidatorCandidates( inputValidatorAddrs, - - maxValidatorNumber + inputTrustedWeights, + maxValidatorNumber, + maxPrioritizedValidatorNumber ); expect(outputValidators).eql(expectingValidatorAddrs); }); it('Shuffled: Actual(prioritized) < MaxNum(prioritized); Actual(regular) > MaxNum(regular)', async () => { // prettier-ignore - let indexes = [0, 1, 2, 3, 4, 5, 6, 7]; + let indexes = [0, 1, 2, 3, 4, 5, 6, 7 ]; let statuses = [true, false, false, false, false, true, true, false]; - await setPriorityStatusByIndexes(indexes, statuses); + let inputTrustedWeights = await setPriorityStatusByIndexes(indexes, statuses); - let sortedIndexes = sortArrayByBoolean(indexes, statuses); - let expectingValidatorAddrs = sortedIndexes - .map((i) => validatorCandidates[i].address) - .slice(0, maxValidatorNumber); + let sortedIndexes = sortArrayByBoolean([...indexes], statuses); + let expectingValidatorAddrs = sortedIndexes.map((i) => candidates[i]).slice(0, maxValidatorNumber); - let inputValidatorAddrs = indexes.map((i) => validatorCandidates[i].address).slice(0, maxValidatorNumber); + let inputValidatorAddrs = indexes.map((i) => candidates[i]).slice(0, maxValidatorNumber); let outputValidators = await validatorContract.arrangeValidatorCandidates( inputValidatorAddrs, - maxValidatorNumber + inputTrustedWeights, + maxValidatorNumber, + maxPrioritizedValidatorNumber ); expect(outputValidators).eql(expectingValidatorAddrs); }); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index ea52ee589..1a76df345 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -17,6 +17,7 @@ import * as RoninValidatorSet from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../helpers/utils'; import { initTest } from '../helpers/fixture'; import { GovernanceAdminInterface } from '../../src/script/governance-admin-interface'; +import { Address } from 'hardhat-deploy/dist/types'; let roninValidatorSet: MockRoninValidatorSetExtended; let stakingContract: Staking; @@ -26,21 +27,25 @@ let governanceAdminInterface: GovernanceAdminInterface; let coinbase: SignerWithAddress; let treasury: SignerWithAddress; +let bridgeOperator: SignerWithAddress; let deployer: SignerWithAddress; let governor: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; let currentValidatorSet: string[]; +const localValidatorCandidatesLength = 5; + const slashFelonyAmount = 100; const maxValidatorNumber = 4; const maxValidatorCandidate = 100; -const minValidatorBalance = BigNumber.from(2); -const bonusPerBlock = BigNumber.from(1); +const minValidatorBalance = BigNumber.from(20000); +const validatorBonusPerBlock = BigNumber.from(5000); +const bridgeOperatorBonusPerBlock = BigNumber.from(37); describe('Ronin Validator Set test', () => { before(async () => { - [coinbase, treasury, deployer, governor, ...validatorCandidates] = await ethers.getSigners(); - validatorCandidates = validatorCandidates.slice(0, 5); + [coinbase, treasury, bridgeOperator, deployer, governor, ...validatorCandidates] = await ethers.getSigners(); + validatorCandidates = validatorCandidates.slice(0, localValidatorCandidatesLength); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); const { slashContractAddress, validatorContractAddress, stakingContractAddress, roninGovernanceAdminAddress } = @@ -50,7 +55,8 @@ describe('Ronin Validator Set test', () => { maxValidatorNumber, maxValidatorCandidate, slashFelonyAmount, - bonusPerBlock, + validatorBonusPerBlock, + bridgeOperatorBonusPerBlock, }); roninValidatorSet = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); @@ -72,146 +78,217 @@ describe('Ronin Validator Set test', () => { await network.provider.send('hardhat_setCoinbase', [ethers.constants.AddressZero]); }); - it('Should not be able to wrap up epoch using unauthorized account', async () => { - await expect(roninValidatorSet.connect(deployer).wrapUpEpoch()).revertedWith( - 'RoninValidatorSet: method caller must be coinbase' - ); - }); + describe('Wrapping up epoch sanity check', async () => { + it('Should not be able to wrap up epoch using unauthorized account', async () => { + await expect(roninValidatorSet.connect(deployer).wrapUpEpoch()).revertedWith( + 'RoninValidatorSet: method caller must be coinbase' + ); + }); - it('Should not be able to wrap up epoch when the epoch is not ending', async () => { - await expect(roninValidatorSet.connect(coinbase).wrapUpEpoch()).revertedWith( - 'RoninValidatorSet: only allowed at the end of epoch' - ); - }); + it('Should not be able to wrap up epoch when the epoch is not ending', async () => { + await expect(roninValidatorSet.connect(coinbase).wrapUpEpoch()).revertedWith( + 'RoninValidatorSet: only allowed at the end of epoch' + ); + }); - it('Should be able to wrap up epoch when the epoch is ending', async () => { - let tx: ContractTransaction; - await mineBatchTxs(async () => { - await roninValidatorSet.endEpoch(); - tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + it('Should be able to wrap up epoch when the epoch is ending', async () => { + let tx: ContractTransaction; + await mineBatchTxs(async () => { + await roninValidatorSet.endEpoch(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); + await RoninValidatorSet.expects.emitWrappedUpEpochEvent(tx!); + await RoninValidatorSet.expects.emitBlockProducerSetUpdatedEvent(tx!, []); + expect(await roninValidatorSet.getValidators()).eql([]); }); - await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, []); - expect(await roninValidatorSet.getValidators()).eql([]); }); - it('Should be able to wrap up epoch and sync validator set from staking contract', async () => { - for (let i = 0; i <= 3; i++) { - await stakingContract - .connect(validatorCandidates[i]) - .applyValidatorCandidate( - validatorCandidates[i].address, - validatorCandidates[i].address, - validatorCandidates[i].address, - 2_00, - { - value: minValidatorBalance.add(i), - } - ); - } + describe('Wrapping up at the end of the epoch', async () => { + it('Should be able to wrap up epoch at end of epoch and not sync the validator set', async () => { + for (let i = 0; i <= 3; i++) { + await stakingContract + .connect(validatorCandidates[i]) + .applyValidatorCandidate( + validatorCandidates[i].address, + validatorCandidates[i].address, + validatorCandidates[i].address, + validatorCandidates[i].address, + 2_00, + { + value: minValidatorBalance.add(i), + } + ); + } + + let tx: ContractTransaction; + await mineBatchTxs(async () => { + await roninValidatorSet.endEpoch(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); - let tx: ContractTransaction; - await mineBatchTxs(async () => { - await roninValidatorSet.endEpoch(); - tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + await RoninValidatorSet.expects.emitWrappedUpEpochEvent(tx!); + expect(await roninValidatorSet.getValidators()).eql([]); + expect(await roninValidatorSet.getBlockProducers()).eql([]); + expect(tx!).not.emit(roninValidatorSet, 'ValidatorSetUpdated'); }); - await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent( - tx!, - validatorCandidates - .slice(0, 4) - .reverse() - .map((_) => _.address) - ); + }); + + describe('Wrapping up at the end of the period', async () => { + let _expectingValidatorsAddr: Address[]; + it('Should be able to wrap up epoch at end of period and sync validator set from staking contract', async () => { + let tx: ContractTransaction; + await mineBatchTxs(async () => { + await roninValidatorSet.endEpoch(); + await roninValidatorSet.endPeriod(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); - expect(await roninValidatorSet.getValidators()).eql( - validatorCandidates + _expectingValidatorsAddr = validatorCandidates .slice(0, 4) .reverse() - .map((_) => _.address) - ); - }); + .map((_) => _.address); - it(`Should be able to wrap up epoch and pick top ${maxValidatorNumber} to be validators`, async () => { - await stakingContract - .connect(coinbase) - .applyValidatorCandidate(coinbase.address, coinbase.address, treasury.address, 1_00 /* 1% */, { value: 100 }); - for (let i = 4; i < validatorCandidates.length; i++) { + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, _expectingValidatorsAddr); + expect(await roninValidatorSet.getValidators()).eql(_expectingValidatorsAddr); + expect(await roninValidatorSet.getBlockProducers()).eql(_expectingValidatorsAddr); + }); + + it('Should isValidator method returns `true` for validator', async () => { + for (let _validatorAddr of _expectingValidatorsAddr) { + expect(await roninValidatorSet.isValidator(_validatorAddr)).eq(true); + } + }); + + it('Should isValidator method returns `false` for non-validator', async () => { + expect(await roninValidatorSet.isValidator(deployer.address)).eq(false); + }); + + it(`Should be able to wrap up epoch at the end of period and pick top ${maxValidatorNumber} to be validators`, async () => { await stakingContract - .connect(validatorCandidates[i]) + .connect(coinbase) .applyValidatorCandidate( - validatorCandidates[i].address, - validatorCandidates[i].address, - validatorCandidates[i].address, - 2_00, + coinbase.address, + coinbase.address, + treasury.address, + bridgeOperator.address, + 1_00 /* 1% */, { - value: minValidatorBalance.add(i), + value: minValidatorBalance.mul(100), } ); - } - expect((await roninValidatorSet.getValidatorCandidates()).length).eq(validatorCandidates.length + 1); - - let tx: ContractTransaction; - await mineBatchTxs(async () => { - await roninValidatorSet.endEpoch(); - tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); - currentValidatorSet = [ - coinbase.address, - ...validatorCandidates - .slice(2) - .reverse() - .map((_) => _.address), - ]; - }); - await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); - expect(await roninValidatorSet.getValidators()).eql(currentValidatorSet); - }); + for (let i = 4; i < localValidatorCandidatesLength; i++) { + await stakingContract + .connect(validatorCandidates[i]) + .applyValidatorCandidate( + validatorCandidates[i].address, + validatorCandidates[i].address, + validatorCandidates[i].address, + validatorCandidates[i].address, + 2_00, + { + value: minValidatorBalance.add(i), + } + ); + } + expect((await roninValidatorSet.getValidatorCandidates()).length).eq(localValidatorCandidatesLength + 1); - it('Should not be able to submit block reward using unauthorized account', async () => { - await expect(roninValidatorSet.submitBlockReward()).revertedWith( - 'RoninValidatorSet: method caller must be coinbase' - ); + let tx: ContractTransaction; + await mineBatchTxs(async () => { + await roninValidatorSet.endEpoch(); + await roninValidatorSet.endPeriod(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + currentValidatorSet = [ + coinbase.address, + ...validatorCandidates + .slice(2) + .reverse() + .map((_) => _.address), + ]; + }); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); + expect(await roninValidatorSet.getValidators()).eql(currentValidatorSet); + expect(await roninValidatorSet.getBlockProducers()).eql(currentValidatorSet); + }); }); - it('Should be able to submit block reward using coinbase account', async () => { - const tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); - await RoninValidatorSet.expects.emitBlockRewardSubmittedEvent(tx, coinbase.address, 100, bonusPerBlock); - }); + describe('Recording and distributing rewards', async () => { + it('Should not be able to submit block reward using unauthorized account', async () => { + await expect(roninValidatorSet.submitBlockReward()).revertedWith( + 'RoninValidatorSet: method caller must be coinbase' + ); + }); - it('Should be able to get right reward at the end of period', async () => { - const balance = await treasury.getBalance(); - let tx: ContractTransaction; - await mineBatchTxs(async () => { - await roninValidatorSet.endEpoch(); - await roninValidatorSet.endPeriod(); - tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + it('Should be able to submit block reward using coinbase account', async () => { + const tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + await RoninValidatorSet.expects.emitBlockRewardSubmittedEvent(tx, coinbase.address, 100, validatorBonusPerBlock); }); - await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); - await RoninValidatorSet.expects.emitStakingRewardDistributedEvent(tx!, bonusPerBlock.add(99)); - await RoninValidatorSet.expects.emitMiningRewardDistributedEvent(tx!, coinbase.address, 1); - const balanceDiff = (await treasury.getBalance()).sub(balance); - expect(balanceDiff).eq(1); // 100 * 1% - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(bonusPerBlock.add(99)); // remain amount (99%) - }); - it('Should not allocate minting fee for the slashed validators', async () => { - let tx: ContractTransaction; - { + it('Should be able to get right reward at the end of period', async () => { const balance = await treasury.getBalance(); - await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); - tx = await slashIndicator.slashMisdemeanor(coinbase.address); - expect(tx).emit(roninValidatorSet, 'ValidatorPunished').withArgs(coinbase.address, 0, 0); - + let tx: ContractTransaction; await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); + await roninValidatorSet.endPeriod(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); - const balanceDiff = (await treasury.getBalance()).sub(balance); - expect(balanceDiff).eq(0); - // The delegators don't receives the new rewards until the period is ended - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(bonusPerBlock.add(99)); await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); - } + await RoninValidatorSet.expects.emitStakingRewardDistributedEvent(tx!, 5049); // (5000 + 100) * 99% + await RoninValidatorSet.expects.emitMiningRewardDistributedEvent(tx!, coinbase.address, treasury.address, 51); // (5000 + 100) * 1% + await RoninValidatorSet.expects.emitBridgeOperatorRewardDistributedEvent( + tx!, + coinbase.address, + treasury.address, + 37 + ); + const balanceDiff = (await treasury.getBalance()).sub(balance); + expect(balanceDiff).eq(88); // = (5000 + 100) * 1% + 37 = (51 + 37) + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + 5049 // (5000 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase + ); + }); + + it('Should not allocate minting fee for the slashed validators, but allocate bridge reward', async () => { + let tx: ContractTransaction; + { + const balance = await treasury.getBalance(); + await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + tx = await slashIndicator.slashMisdemeanor(coinbase.address); + expect(tx).emit(roninValidatorSet, 'ValidatorPunished').withArgs(coinbase.address, 0, 0); - { + await mineBatchTxs(async () => { + await roninValidatorSet.endEpoch(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); + const balanceDiff = (await treasury.getBalance()).sub(balance); + expect(balanceDiff).eq(0); + // The delegators don't receives the new rewards until the period is ended + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + 5049 // (5000 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase + ); + await RoninValidatorSet.expects.emitWrappedUpEpochEvent(tx!); + expect(tx!).not.emit(roninValidatorSet, 'ValidatorSetUpdated'); + } + + { + const balance = await treasury.getBalance(); + await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + await mineBatchTxs(async () => { + await roninValidatorSet.endEpoch(); + await roninValidatorSet.endPeriod(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); + const balanceDiff = (await treasury.getBalance()).sub(balance); + expect(balanceDiff).eq(bridgeOperatorBonusPerBlock); // bridge bonus + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + 5049 // (5000 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase + ); + await RoninValidatorSet.expects.emitWrappedUpEpochEvent(tx!); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); + } + }); + + it('Should be able to record delegating reward for a successful period', async () => { + let tx: ContractTransaction; const balance = await treasury.getBalance(); await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); await mineBatchTxs(async () => { @@ -219,47 +296,38 @@ describe('Ronin Validator Set test', () => { await roninValidatorSet.endPeriod(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); - const balanceDiff = (await treasury.getBalance()).sub(balance); - expect(balanceDiff).eq(0); - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq(bonusPerBlock.add(99)); + await RoninValidatorSet.expects.emitWrappedUpEpochEvent(tx!); await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); - } - }); + await RoninValidatorSet.expects.emitStakingRewardDistributedEvent( + tx!, + validatorBonusPerBlock.add(100).div(100).mul(99) + ); - it('Should be able to record delegating reward for a successful period', async () => { - let tx: ContractTransaction; - const balance = await treasury.getBalance(); - await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); - await mineBatchTxs(async () => { - await roninValidatorSet.endEpoch(); - await roninValidatorSet.endPeriod(); - tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + const balanceDiff = (await treasury.getBalance()).sub(balance); + const expectingBalanceDiff = validatorBonusPerBlock.add(100).div(100).add(bridgeOperatorBonusPerBlock); + expect(balanceDiff).eq(expectingBalanceDiff); + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + validatorBonusPerBlock.add(100).div(100).mul(99).mul(2) + ); }); - const balanceDiff = (await treasury.getBalance()).sub(balance); - expect(balanceDiff).eq(1); - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( - bonusPerBlock.add(99).mul(2) - ); - await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); - await RoninValidatorSet.expects.emitStakingRewardDistributedEvent(tx!, bonusPerBlock.add(99)); - }); - it('Should not allocate reward for the slashed validator', async () => { - let tx: ContractTransaction; - const balance = await treasury.getBalance(); - await slashIndicator.slashMisdemeanor(coinbase.address); - tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); - await RoninValidatorSet.expects.emitRewardDeprecatedEvent(tx!, coinbase.address, 100); - await mineBatchTxs(async () => { - await roninValidatorSet.endEpoch(); - await roninValidatorSet.endPeriod(); - tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + it('Should not allocate reward for the slashed validator', async () => { + let tx: ContractTransaction; + const balance = await treasury.getBalance(); + await slashIndicator.slashMisdemeanor(coinbase.address); + tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + await RoninValidatorSet.expects.emitRewardDeprecatedEvent(tx!, coinbase.address, 100); + await mineBatchTxs(async () => { + await roninValidatorSet.endEpoch(); + await roninValidatorSet.endPeriod(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); + const balanceDiff = (await treasury.getBalance()).sub(balance); + expect(balanceDiff).eq(0); + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + validatorBonusPerBlock.add(100).div(100).mul(99).mul(2) + ); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); }); - const balanceDiff = (await treasury.getBalance()).sub(balance); - expect(balanceDiff).eq(0); - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( - bonusPerBlock.add(99).mul(2) - ); - await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); }); }); From c380c4d2437069e68d4866b8e564eef812696ef5 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Mon, 24 Oct 2022 18:20:30 +0700 Subject: [PATCH 176/190] Add Bridge contracts (#30) * Remove bridge operator weight * Add bridge contracts * Update RoninTrustedOrganization * Slash bridge voting feature * Avoid slashing newcomers --- contracts/extensions/GatewayV2.sol | 98 ++++ contracts/extensions/MinimumWithdrawal.sol | 61 +++ contracts/extensions/WithdrawalLimitation.sol | 324 +++++++++++++ .../HasRoninGovernanceAdminContract.sol | 46 ++ .../BOsGovernanceProposal.sol | 21 +- .../BOsGovernanceRelay.sol | 9 +- .../sequential-governance/GovernanceRelay.sol | 6 +- contracts/interfaces/IBridge.sol | 8 +- contracts/interfaces/IERC20Mintable.sol | 6 + contracts/interfaces/IERC721Mintable.sol | 6 + contracts/interfaces/IMainchainGatewayV2.sol | 120 +++++ contracts/interfaces/IRoninGatewayV2.sol | 149 ++++++ .../interfaces/IRoninGovernanceAdmin.sol | 9 + .../interfaces/IRoninTrustedOrganization.sol | 82 +++- contracts/interfaces/ISlashIndicator.sol | 36 +- contracts/interfaces/IWETH.sol | 10 + .../IHasRoninGovernanceAdminContract.sol | 25 + .../consumers/MappedTokenConsumer.sol | 11 + contracts/libraries/BridgeOperatorsBallot.sol | 31 +- contracts/libraries/Token.sol | 216 +++++++++ contracts/libraries/Transfer.sol | 103 ++++ contracts/mainchain/MainchainGatewayV2.sol | 453 ++++++++++++++++++ .../mainchain/MainchainGovernanceAdmin.sol | 31 +- contracts/mocks/MockBridge.sol | 6 +- .../multi-chains/RoninTrustedOrganization.sol | 344 ++++++++++--- contracts/ronin/RoninGatewayV2.sol | 438 +++++++++++++++++ contracts/ronin/RoninGovernanceAdmin.sol | 36 +- contracts/ronin/SlashIndicator.sol | 64 +++ .../ronin/validator/RoninValidatorSet.sol | 2 +- src/config.ts | 18 +- src/deploy/proxy/slash-indicator-proxy.ts | 4 + src/script/governance-admin-interface.ts | 21 +- src/script/proposal.ts | 32 +- test/governance-admin/GovernanceAdmin.test.ts | 16 +- test/helpers/fixture.ts | 4 + .../integration/ActionSlashValidators.test.ts | 10 +- test/integration/ActionSubmitReward.test.ts | 8 +- test/integration/ActionWrapUpEpoch.test.ts | 8 +- test/integration/Configuration.test.ts | 8 +- test/maintainance/Maintenance.test.ts | 8 +- test/slash/SlashIndicator.test.ts | 8 +- test/validator/ArrangeValidators.test.ts | 41 +- test/validator/RoninValidatorSet.test.ts | 8 +- 43 files changed, 2748 insertions(+), 197 deletions(-) create mode 100644 contracts/extensions/GatewayV2.sol create mode 100644 contracts/extensions/MinimumWithdrawal.sol create mode 100644 contracts/extensions/WithdrawalLimitation.sol create mode 100644 contracts/extensions/collections/HasRoninGovernanceAdminContract.sol create mode 100644 contracts/interfaces/IERC20Mintable.sol create mode 100644 contracts/interfaces/IERC721Mintable.sol create mode 100644 contracts/interfaces/IMainchainGatewayV2.sol create mode 100644 contracts/interfaces/IRoninGatewayV2.sol create mode 100644 contracts/interfaces/IRoninGovernanceAdmin.sol create mode 100644 contracts/interfaces/IWETH.sol create mode 100644 contracts/interfaces/collections/IHasRoninGovernanceAdminContract.sol create mode 100644 contracts/interfaces/consumers/MappedTokenConsumer.sol create mode 100644 contracts/libraries/Token.sol create mode 100644 contracts/libraries/Transfer.sol create mode 100644 contracts/mainchain/MainchainGatewayV2.sol create mode 100644 contracts/ronin/RoninGatewayV2.sol diff --git a/contracts/extensions/GatewayV2.sol b/contracts/extensions/GatewayV2.sol new file mode 100644 index 000000000..0dfc8625a --- /dev/null +++ b/contracts/extensions/GatewayV2.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/security/Pausable.sol"; +import "../interfaces/IQuorum.sol"; +import "./collections/HasProxyAdmin.sol"; + +abstract contract GatewayV2 is HasProxyAdmin, Pausable, IQuorum { + uint256 internal _num; + uint256 internal _denom; + + address private ______deprecated; + uint256 public nonce; + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + */ + uint256[50] private ______gap; + + /** + * @dev See {IQuorum-getThreshold}. + */ + function getThreshold() external view virtual returns (uint256, uint256) { + return (_num, _denom); + } + + /** + * @dev See {IQuorum-checkThreshold}. + */ + function checkThreshold(uint256 _voteWeight) external view virtual returns (bool) { + return _voteWeight * _denom >= _num * _getTotalWeight(); + } + + /** + * @dev See {IQuorum-setThreshold}. + */ + function setThreshold(uint256 _numerator, uint256 _denominator) + external + virtual + onlyAdmin + returns (uint256, uint256) + { + return _setThreshold(_numerator, _denominator); + } + + /** + * @dev Triggers paused state. + */ + function pause() external onlyAdmin { + _pause(); + } + + /** + * @dev Triggers unpaused state. + */ + function unpause() external onlyAdmin { + _unpause(); + } + + /** + * @dev See {IQuorum-minimumVoteWeight}. + */ + function minimumVoteWeight() public view virtual returns (uint256) { + return _minimumVoteWeight(_getTotalWeight()); + } + + /** + * @dev Sets threshold and returns the old one. + * + * Emits the `ThresholdUpdated` event. + * + */ + function _setThreshold(uint256 _numerator, uint256 _denominator) + internal + virtual + returns (uint256 _previousNum, uint256 _previousDenom) + { + require(_numerator <= _denominator, "GatewayV2: invalid threshold"); + _previousNum = _num; + _previousDenom = _denom; + _num = _numerator; + _denom = _denominator; + emit ThresholdUpdated(nonce++, _numerator, _denominator, _previousNum, _previousDenom); + } + + /** + * @dev Returns minimum vote weight. + */ + function _minimumVoteWeight(uint256 _totalWeight) internal view virtual returns (uint256) { + return (_num * _totalWeight + _denom - 1) / _denom; + } + + /** + * @dev Returns the total weight. + */ + function _getTotalWeight() internal view virtual returns (uint256); +} diff --git a/contracts/extensions/MinimumWithdrawal.sol b/contracts/extensions/MinimumWithdrawal.sol new file mode 100644 index 000000000..e06eddf5a --- /dev/null +++ b/contracts/extensions/MinimumWithdrawal.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./collections/HasProxyAdmin.sol"; +import "../libraries/Transfer.sol"; + +abstract contract MinimumWithdrawal is HasProxyAdmin { + /// @dev Emitted when the minimum thresholds are updated + event MinimumThresholdsUpdated(address[] tokens, uint256[] threshold); + + /// @dev Mapping from token address => minimum thresholds + mapping(address => uint256) public minimumThreshold; + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + */ + uint256[50] private ______gap; + + /** + * @dev Sets the minimum thresholds to withdraw. + * + * Requirements: + * - The method caller is admin. + * - The arrays have the same length and its length larger than 0. + * + * Emits the `MinimumThresholdsUpdated` event. + * + */ + function setMinimumThresholds(address[] calldata _tokens, uint256[] calldata _thresholds) external virtual onlyAdmin { + require(_tokens.length > 0, "MinimumWithdrawal: invalid array length"); + _setMinimumThresholds(_tokens, _thresholds); + } + + /** + * @dev Sets minimum thresholds. + * + * Requirements: + * - The array lengths are equal. + * + * Emits the `MinimumThresholdsUpdated` event. + * + */ + function _setMinimumThresholds(address[] calldata _tokens, uint256[] calldata _thresholds) internal virtual { + require(_tokens.length == _thresholds.length, "MinimumWithdrawal: invalid array length"); + for (uint256 _i; _i < _tokens.length; _i++) { + minimumThreshold[_tokens[_i]] = _thresholds[_i]; + } + emit MinimumThresholdsUpdated(_tokens, _thresholds); + } + + /** + * @dev Checks whether the request is larger than or equal to the minimum threshold. + */ + function _checkWithdrawal(Transfer.Request calldata _request) internal view { + require( + _request.info.erc != Token.Standard.ERC20 || _request.info.quantity >= minimumThreshold[_request.tokenAddr], + "MinimumWithdrawal: query for too small quantity" + ); + } +} diff --git a/contracts/extensions/WithdrawalLimitation.sol b/contracts/extensions/WithdrawalLimitation.sol new file mode 100644 index 000000000..59b1471e6 --- /dev/null +++ b/contracts/extensions/WithdrawalLimitation.sol @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./GatewayV2.sol"; + +abstract contract WithdrawalLimitation is GatewayV2 { + /// @dev Emitted when the high-tier vote weight threshold is updated + event HighTierVoteWeightThresholdUpdated( + uint256 indexed nonce, + uint256 indexed numerator, + uint256 indexed denominator, + uint256 previousNumerator, + uint256 previousDenominator + ); + /// @dev Emitted when the thresholds for high-tier withdrawals that requires high-tier vote weights are updated + event HighTierThresholdsUpdated(address[] tokens, uint256[] thresholds); + /// @dev Emitted when the thresholds for locked withdrawals are updated + event LockedThresholdsUpdated(address[] tokens, uint256[] thresholds); + /// @dev Emitted when the fee percentages to unlock withdraw are updated + event UnlockFeePercentagesUpdated(address[] tokens, uint256[] percentages); + /// @dev Emitted when the daily limit thresholds are updated + event DailyWithdrawalLimitsUpdated(address[] tokens, uint256[] limits); + + uint256 public constant _MAX_PERCENTAGE = 1_000_000; + + uint256 internal _highTierVWNum; + uint256 internal _highTierVWDenom; + + /// @dev Mapping from mainchain token => the amount thresholds for high-tier withdrawals that requires high-tier vote weights + mapping(address => uint256) public highTierThreshold; + /// @dev Mapping from mainchain token => the amount thresholds to lock withdrawal + mapping(address => uint256) public lockedThreshold; + /// @dev Mapping from mainchain token => unlock fee percentages for unlocker + /// @notice Values 0-1,000,000 map to 0%-100% + mapping(address => uint256) public unlockFeePercentages; + /// @dev Mapping from mainchain token => daily limit amount for withdrawal + mapping(address => uint256) public dailyWithdrawalLimit; + /// @dev Mapping from token address => today withdrawal amount + mapping(address => uint256) public lastSyncedWithdrawal; + /// @dev Mapping from token address => last date synced to record the `lastSyncedWithdrawal` + mapping(address => uint256) public lastDateSynced; + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + */ + uint256[50] private ______gap; + + /** + * @dev Override {GatewayV2-setThreshold}. + * + * Requirements: + * - The high-tier vote weight threshold must equal to or larger than the normal threshold. + * + */ + function setThreshold(uint256 _numerator, uint256 _denominator) + external + virtual + override + onlyAdmin + returns (uint256 _previousNum, uint256 _previousDenom) + { + (_previousNum, _previousDenom) = _setThreshold(_numerator, _denominator); + _verifyThresholds(); + } + + /** + * @dev Returns the high-tier vote weight threshold. + */ + function getHighTierVoteWeightThreshold() external view virtual returns (uint256, uint256) { + return (_highTierVWNum, _highTierVWDenom); + } + + /** + * @dev Checks whether the `_voteWeight` passes the high-tier vote weight threshold. + */ + function checkHighTierVoteWeightThreshold(uint256 _voteWeight) external view virtual returns (bool) { + return _voteWeight * _highTierVWDenom >= _highTierVWNum * _getTotalWeight(); + } + + /** + * @dev Sets high-tier vote weight threshold and returns the old one. + * + * Requirements: + * - The method caller is admin. + * - The high-tier vote weight threshold must equal to or larger than the normal threshold. + * + * Emits the `HighTierVoteWeightThresholdUpdated` event. + * + */ + function setHighTierVoteWeightThreshold(uint256 _numerator, uint256 _denominator) + external + virtual + onlyAdmin + returns (uint256 _previousNum, uint256 _previousDenom) + { + (_previousNum, _previousDenom) = _setHighTierVoteWeightThreshold(_numerator, _denominator); + _verifyThresholds(); + } + + /** + * @dev Sets the thresholds for high-tier withdrawals that requires high-tier vote weights. + * + * Requirements: + * - The method caller is admin. + * - The arrays have the same length and its length larger than 0. + * + * Emits the `HighTierThresholdsUpdated` event. + * + */ + function setHighTierThresholds(address[] calldata _tokens, uint256[] calldata _thresholds) + external + virtual + onlyAdmin + { + require(_tokens.length > 0, "WithdrawalLimitation: invalid array length"); + _setHighTierThresholds(_tokens, _thresholds); + } + + /** + * @dev Sets the amount thresholds to lock withdrawal. + * + * Requirements: + * - The method caller is admin. + * - The arrays have the same length and its length larger than 0. + * + * Emits the `LockedThresholdsUpdated` event. + * + */ + function setLockedThresholds(address[] calldata _tokens, uint256[] calldata _thresholds) external virtual onlyAdmin { + require(_tokens.length > 0, "WithdrawalLimitation: invalid array length"); + _setLockedThresholds(_tokens, _thresholds); + } + + /** + * @dev Sets fee percentages to unlock withdrawal. + * + * Requirements: + * - The method caller is admin. + * - The arrays have the same length and its length larger than 0. + * + * Emits the `UnlockFeePercentagesUpdated` event. + * + */ + function setUnlockFeePercentages(address[] calldata _tokens, uint256[] calldata _percentages) + external + virtual + onlyAdmin + { + require(_tokens.length > 0, "WithdrawalLimitation: invalid array length"); + _setUnlockFeePercentages(_tokens, _percentages); + } + + /** + * @dev Sets daily limit amounts for the withdrawals. + * + * Requirements: + * - The method caller is admin. + * - The arrays have the same length and its length larger than 0. + * + * Emits the `DailyWithdrawalLimitsUpdated` event. + * + */ + function setDailyWithdrawalLimits(address[] calldata _tokens, uint256[] calldata _limits) external virtual onlyAdmin { + require(_tokens.length > 0, "WithdrawalLimitation: invalid array length"); + _setDailyWithdrawalLimits(_tokens, _limits); + } + + /** + * @dev Checks whether the withdrawal reaches the limitation. + */ + function reachedWithdrawalLimit(address _token, uint256 _quantity) external view virtual returns (bool) { + return _reachedWithdrawalLimit(_token, _quantity); + } + + /** + * @dev Sets high-tier vote weight threshold and returns the old one. + * + * Emits the `HighTierVoteWeightThresholdUpdated` event. + * + */ + function _setHighTierVoteWeightThreshold(uint256 _numerator, uint256 _denominator) + internal + returns (uint256 _previousNum, uint256 _previousDenom) + { + require(_numerator <= _denominator, "WithdrawalLimitation: invalid threshold"); + _previousNum = _highTierVWNum; + _previousDenom = _highTierVWDenom; + _highTierVWNum = _numerator; + _highTierVWDenom = _denominator; + emit HighTierVoteWeightThresholdUpdated(nonce++, _numerator, _denominator, _previousNum, _previousDenom); + } + + /** + * @dev Sets the thresholds for high-tier withdrawals that requires high-tier vote weights. + * + * Requirements: + * - The array lengths are equal. + * + * Emits the `HighTierThresholdsUpdated` event. + * + */ + function _setHighTierThresholds(address[] calldata _tokens, uint256[] calldata _thresholds) internal virtual { + require(_tokens.length == _thresholds.length, "WithdrawalLimitation: invalid array length"); + for (uint256 _i; _i < _tokens.length; _i++) { + highTierThreshold[_tokens[_i]] = _thresholds[_i]; + } + emit HighTierThresholdsUpdated(_tokens, _thresholds); + } + + /** + * @dev Sets the amount thresholds to lock withdrawal. + * + * Requirements: + * - The array lengths are equal. + * + * Emits the `LockedThresholdsUpdated` event. + * + */ + function _setLockedThresholds(address[] calldata _tokens, uint256[] calldata _thresholds) internal virtual { + require(_tokens.length == _thresholds.length, "WithdrawalLimitation: invalid array length"); + for (uint256 _i; _i < _tokens.length; _i++) { + lockedThreshold[_tokens[_i]] = _thresholds[_i]; + } + emit LockedThresholdsUpdated(_tokens, _thresholds); + } + + /** + * @dev Sets fee percentages to unlock withdrawal. + * + * Requirements: + * - The array lengths are equal. + * - The percentage is equal to or less than 100_000. + * + * Emits the `UnlockFeePercentagesUpdated` event. + * + */ + function _setUnlockFeePercentages(address[] calldata _tokens, uint256[] calldata _percentages) internal virtual { + require(_tokens.length == _percentages.length, "WithdrawalLimitation: invalid array length"); + for (uint256 _i; _i < _tokens.length; _i++) { + require(_percentages[_i] <= _MAX_PERCENTAGE, "WithdrawalLimitation: invalid percentage"); + unlockFeePercentages[_tokens[_i]] = _percentages[_i]; + } + emit UnlockFeePercentagesUpdated(_tokens, _percentages); + } + + /** + * @dev Sets daily limit amounts for the withdrawals. + * + * Requirements: + * - The array lengths are equal. + * + * Emits the `DailyWithdrawalLimitsUpdated` event. + * + */ + function _setDailyWithdrawalLimits(address[] calldata _tokens, uint256[] calldata _limits) internal virtual { + require(_tokens.length == _limits.length, "WithdrawalLimitation: invalid array length"); + for (uint256 _i; _i < _tokens.length; _i++) { + dailyWithdrawalLimit[_tokens[_i]] = _limits[_i]; + } + emit DailyWithdrawalLimitsUpdated(_tokens, _limits); + } + + /** + * @dev Checks whether the withdrawal reaches the daily limitation. + * + * Requirements: + * - The daily withdrawal threshold should not apply for locked withdrawals. + * + */ + function _reachedWithdrawalLimit(address _token, uint256 _quantity) internal view virtual returns (bool) { + if (_lockedWithdrawalRequest(_token, _quantity)) { + return false; + } + + uint256 _currentDate = block.timestamp / 1 days; + if (_currentDate > lastDateSynced[_token]) { + return dailyWithdrawalLimit[_token] <= _quantity; + } else { + return dailyWithdrawalLimit[_token] <= lastSyncedWithdrawal[_token] + _quantity; + } + } + + /** + * @dev Record withdrawal token. + */ + function _recordWithdrawal(address _token, uint256 _quantity) internal virtual { + uint256 _currentDate = block.timestamp / 1 days; + if (_currentDate > lastDateSynced[_token]) { + lastDateSynced[_token] = _currentDate; + lastSyncedWithdrawal[_token] = _quantity; + } else { + lastSyncedWithdrawal[_token] += _quantity; + } + } + + /** + * @dev Returns whether the withdrawal request is locked or not. + */ + function _lockedWithdrawalRequest(address _token, uint256 _quantity) internal view virtual returns (bool) { + return lockedThreshold[_token] <= _quantity; + } + + /** + * @dev Computes fee percentage. + */ + function _computeFeePercentage(uint256 _amount, uint256 _percentage) internal view virtual returns (uint256) { + return (_amount * _percentage) / _MAX_PERCENTAGE; + } + + /** + * @dev Returns high-tier vote weight. + */ + function _highTierVoteWeight(uint256 _totalWeight) internal view virtual returns (uint256) { + return (_highTierVWNum * _totalWeight + _highTierVWDenom - 1) / _highTierVWDenom; + } + + /** + * @dev Validates whether the high-tier vote weight threshold is larger than the normal threshold. + */ + function _verifyThresholds() internal view { + require(_num * _highTierVWDenom <= _highTierVWNum * _denom, "WithdrawalLimitation: invalid thresholds"); + } +} diff --git a/contracts/extensions/collections/HasRoninGovernanceAdminContract.sol b/contracts/extensions/collections/HasRoninGovernanceAdminContract.sol new file mode 100644 index 000000000..f53240e80 --- /dev/null +++ b/contracts/extensions/collections/HasRoninGovernanceAdminContract.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./HasProxyAdmin.sol"; +import "../../interfaces/collections/IHasRoninGovernanceAdminContract.sol"; +import "../../interfaces/IRoninGovernanceAdmin.sol"; + +contract HasRoninGovernanceAdminContract is IHasRoninGovernanceAdminContract, HasProxyAdmin { + IRoninGovernanceAdmin internal _roninGovernanceAdminContract; + + modifier onlyRoninGovernanceAdminContract() { + require( + roninGovernanceAdminContract() == msg.sender, + "HasRoninGovernanceAdminContract: method caller must be ronin governance admin contract" + ); + _; + } + + /** + * @inheritdoc IHasRoninGovernanceAdminContract + */ + function roninGovernanceAdminContract() public view override returns (address) { + return address(_roninGovernanceAdminContract); + } + + /** + * @inheritdoc IHasRoninGovernanceAdminContract + */ + function setRoninGovernanceAdminContract(address _addr) external override onlyAdmin { + _setRoninGovernanceAdminContract(_addr); + } + + /** + * @dev Sets the ronin governance admin contract. + * + * Requirements: + * - The new address is a contract. + * + * Emits the event `RoninGovernanceAdminContractUpdated`. + * + */ + function _setRoninGovernanceAdminContract(address _addr) internal { + _roninGovernanceAdminContract = IRoninGovernanceAdmin(_addr); + emit RoninGovernanceAdminContractUpdated(_addr); + } +} diff --git a/contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceProposal.sol b/contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceProposal.sol index e303dfb56..798e47589 100644 --- a/contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceProposal.sol +++ b/contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceProposal.sol @@ -2,21 +2,28 @@ pragma solidity ^0.8.0; import "../../../extensions/isolated-governance/IsolatedGovernance.sol"; -import "../../../interfaces/consumers/WeightedAddressConsumer.sol"; import "../../../interfaces/consumers/SignatureConsumer.sol"; import "../../../libraries/BridgeOperatorsBallot.sol"; +import "../../../interfaces/IRoninGovernanceAdmin.sol"; -abstract contract BOsGovernanceProposal is SignatureConsumer, WeightedAddressConsumer, IsolatedGovernance { +abstract contract BOsGovernanceProposal is SignatureConsumer, IsolatedGovernance, IRoninGovernanceAdmin { /// @dev The last period that the brige operators synced. uint256 internal _lastSyncedPeriod; /// @dev Mapping from period index => bridge operators vote mapping(uint256 => IsolatedVote) internal _vote; - /// @dev Mapping from governor address => last block that the governor voted + /// @dev Mapping from bridge voter address => last block that the address voted mapping(address => uint256) internal _lastVotedBlock; /// @dev Mapping from period => voter => signatures mapping(uint256 => mapping(address => Signature)) internal _votingSig; + /** + * @inheritdoc IRoninGovernanceAdmin + */ + function lastVotedBlock(address _bridgeVoter) external view returns (uint256) { + return _lastVotedBlock[_bridgeVoter]; + } + /** * @dev Votes for a set of bridge operators by signatures. * @@ -27,7 +34,7 @@ abstract contract BOsGovernanceProposal is SignatureConsumer, WeightedAddressCon * */ function _castVotesBySignatures( - WeightedAddress[] calldata _operators, + address[] calldata _operators, Signature[] calldata _signatures, uint256 _period, uint256 _minimumVoteWeight, @@ -50,7 +57,7 @@ abstract contract BOsGovernanceProposal is SignatureConsumer, WeightedAddressCon require(_lastSigner < _signer, "BOsGovernanceProposal: invalid order"); _lastSigner = _signer; - uint256 _weight = _getWeight(_signer); + uint256 _weight = _getBridgeVoterWeight(_signer); if (_weight > 0) { _hasValidVotes = true; _lastVotedBlock[_signer] = block.number; @@ -65,7 +72,7 @@ abstract contract BOsGovernanceProposal is SignatureConsumer, WeightedAddressCon } /** - * @dev Returns the weight of a governor. + * @dev Returns the weight of a bridge voter. */ - function _getWeight(address _governor) internal view virtual returns (uint256); + function _getBridgeVoterWeight(address _bridgeVoter) internal view virtual returns (uint256); } diff --git a/contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceRelay.sol b/contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceRelay.sol index 3bc816e1b..4a33acccc 100644 --- a/contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceRelay.sol +++ b/contracts/extensions/isolated-governance/bridge-operator-governance/BOsGovernanceRelay.sol @@ -2,11 +2,10 @@ pragma solidity ^0.8.0; import "../../../extensions/isolated-governance/IsolatedGovernance.sol"; -import "../../../interfaces/consumers/WeightedAddressConsumer.sol"; import "../../../interfaces/consumers/SignatureConsumer.sol"; import "../../../libraries/BridgeOperatorsBallot.sol"; -abstract contract BOsGovernanceRelay is SignatureConsumer, WeightedAddressConsumer, IsolatedGovernance { +abstract contract BOsGovernanceRelay is SignatureConsumer, IsolatedGovernance { /// @dev The last period that the brige operators synced. uint256 internal _lastSyncedPeriod; /// @dev Mapping from period index => bridge operators vote @@ -24,7 +23,7 @@ abstract contract BOsGovernanceRelay is SignatureConsumer, WeightedAddressConsum * */ function _relayVotesBySignatures( - WeightedAddress[] calldata _operators, + address[] calldata _operators, Signature[] calldata _signatures, uint256 _period, uint256 _minimumVoteWeight, @@ -47,7 +46,7 @@ abstract contract BOsGovernanceRelay is SignatureConsumer, WeightedAddressConsum } IsolatedVote storage _v = _vote[_period]; - uint256 _totalVoteWeight = _getWeights(_signers); + uint256 _totalVoteWeight = _sumBridgeVoterWeights(_signers); if (_totalVoteWeight >= _minimumVoteWeight) { require(_totalVoteWeight > 0, "BOsGovernanceRelay: invalid vote weight"); _v.status = VoteStatus.Approved; @@ -61,5 +60,5 @@ abstract contract BOsGovernanceRelay is SignatureConsumer, WeightedAddressConsum /** * @dev Returns the weight of the governor list. */ - function _getWeights(address[] memory _governors) internal view virtual returns (uint256); + function _sumBridgeVoterWeights(address[] memory _bridgeVoters) internal view virtual returns (uint256); } diff --git a/contracts/extensions/sequential-governance/GovernanceRelay.sol b/contracts/extensions/sequential-governance/GovernanceRelay.sol index 241042309..358a973f3 100644 --- a/contracts/extensions/sequential-governance/GovernanceRelay.sol +++ b/contracts/extensions/sequential-governance/GovernanceRelay.sol @@ -58,7 +58,7 @@ abstract contract GovernanceRelay is CoreGovernance { ProposalVote storage _vote = vote[_proposal.chainId][_proposal.nonce]; uint256 _minimumForVoteWeight = _getMinimumVoteWeight(); - uint256 _totalForVoteWeight = _getWeights(_forVoteSigners); + uint256 _totalForVoteWeight = _sumWeights(_forVoteSigners); if (_totalForVoteWeight >= _minimumForVoteWeight) { require(_totalForVoteWeight > 0, "GovernanceRelay: invalid vote weight"); _vote.status = VoteStatus.Approved; @@ -68,7 +68,7 @@ abstract contract GovernanceRelay is CoreGovernance { } uint256 _minimumAgainstVoteWeight = _getTotalWeights() - _minimumForVoteWeight + 1; - uint256 _totalAgainstVoteWeight = _getWeights(_againstVoteSigners); + uint256 _totalAgainstVoteWeight = _sumWeights(_againstVoteSigners); if (_totalAgainstVoteWeight >= _minimumAgainstVoteWeight) { require(_totalAgainstVoteWeight > 0, "GovernanceRelay: invalid vote weight"); _vote.status = VoteStatus.Rejected; @@ -139,5 +139,5 @@ abstract contract GovernanceRelay is CoreGovernance { /** * @dev Returns the weight of the governor list. */ - function _getWeights(address[] memory _governors) internal view virtual returns (uint256); + function _sumWeights(address[] memory _governors) internal view virtual returns (uint256); } diff --git a/contracts/interfaces/IBridge.sol b/contracts/interfaces/IBridge.sol index 71e81fd2d..75396d982 100644 --- a/contracts/interfaces/IBridge.sol +++ b/contracts/interfaces/IBridge.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "./consumers/WeightedAddressConsumer.sol"; - -interface IBridge is WeightedAddressConsumer { +interface IBridge { /** * @dev Replaces the old bridge operator list by the new one. * @@ -13,10 +11,10 @@ interface IBridge is WeightedAddressConsumer { * Emitted the event `BridgeOperatorsReplaced`. * */ - function replaceBridgeOperators(WeightedAddress[] calldata) external; + function replaceBridgeOperators(address[] calldata) external; /** * @dev Returns the bridge operator list. */ - function getBridgeOperators() external view returns (WeightedAddress[] memory); + function getBridgeOperators() external view returns (address[] memory); } diff --git a/contracts/interfaces/IERC20Mintable.sol b/contracts/interfaces/IERC20Mintable.sol new file mode 100644 index 000000000..68b4a37ac --- /dev/null +++ b/contracts/interfaces/IERC20Mintable.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.2; + +interface IERC20Mintable { + function mint(address _to, uint256 _value) external returns (bool _success); +} diff --git a/contracts/interfaces/IERC721Mintable.sol b/contracts/interfaces/IERC721Mintable.sol new file mode 100644 index 000000000..a1cfa0056 --- /dev/null +++ b/contracts/interfaces/IERC721Mintable.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IERC721Mintable { + function mint(address _to, uint256 _tokenId) external returns (bool); +} diff --git a/contracts/interfaces/IMainchainGatewayV2.sol b/contracts/interfaces/IMainchainGatewayV2.sol new file mode 100644 index 000000000..05eb42628 --- /dev/null +++ b/contracts/interfaces/IMainchainGatewayV2.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IBridge.sol"; +import "./IWETH.sol"; +import "./consumers/SignatureConsumer.sol"; +import "./consumers/MappedTokenConsumer.sol"; +import "../libraries/Transfer.sol"; + +interface IMainchainGatewayV2 is SignatureConsumer, MappedTokenConsumer, IBridge { + /// @dev Emitted when the deposit is requested + event DepositRequested(bytes32 receiptHash, Transfer.Receipt receipt); + /// @dev Emitted when the assets are withdrawn + event Withdrew(bytes32 receiptHash, Transfer.Receipt receipt); + /// @dev Emitted when the tokens are mapped + event TokenMapped(address[] mainchainTokens, address[] roninTokens, Token.Standard[] standards); + /// @dev Emitted when the wrapped native token contract is updated + event WrappedNativeTokenContractUpdated(IWETH weth); + /// @dev Emitted when the withdrawal is locked + event WithdrawalLocked(bytes32 receiptHash, Transfer.Receipt receipt); + /// @dev Emitted when the withdrawal is unlocked + event WithdrawalUnlocked(bytes32 receiptHash, Transfer.Receipt receipt); + + /** + * @dev Returns the domain seperator. + */ + function DOMAIN_SEPARATOR() external view returns (bytes32); + + /** + * @dev Returns deposit count. + */ + function depositCount() external view returns (uint256); + + /** + * @dev Sets the wrapped native token contract. + * + * Requirements: + * - The method caller is admin. + * + * Emits the `WrappedNativeTokenContractUpdated` event. + * + */ + function setWrappedNativeTokenContract(IWETH _wrappedToken) external; + + /** + * @dev Returns whether the withdrawal is locked. + */ + function withdrawalLocked(uint256 withdrawalId) external view returns (bool); + + /** + * @dev Returns the withdrawal hash. + */ + function withdrawalHash(uint256 withdrawalId) external view returns (bytes32); + + /** + * @dev Locks the assets and request deposit. + */ + function requestDepositFor(Transfer.Request calldata _request) external payable; + + /** + * @dev Withdraws based on the receipt and the validator signatures. + * Returns whether the withdrawal is locked. + * + * Emits the `Withdrew` once the assets are released. + * + */ + function submitWithdrawal(Transfer.Receipt memory _receipt, Signature[] memory _signatures) + external + returns (bool _locked); + + /** + * @dev Approves a specific withdrawal. + * + * Requirements: + * - The method caller is a validator. + * + * Emits the `Withdrew` once the assets are released. + * + */ + function unlockWithdrawal(Transfer.Receipt calldata _receipt) external; + + /** + * @dev Maps mainchain tokens to Ronin network. + * + * Requirement: + * - The method caller is admin. + * - The arrays have the same length and its length larger than 0. + * + * Emits the `TokenMapped` event. + * + */ + function mapTokens( + address[] calldata _mainchainTokens, + address[] calldata _roninTokens, + Token.Standard[] calldata _standards + ) external; + + /** + * @dev Maps mainchain tokens to Ronin network and sets thresholds. + * + * Requirement: + * - The method caller is admin. + * - The arrays have the same length and its length larger than 0. + * + * Emits the `TokenMapped` event. + * + */ + function mapTokensAndThresholds( + address[] calldata _mainchainTokens, + address[] calldata _roninTokens, + Token.Standard[] calldata _standards, + uint256[][4] calldata _thresholds + ) external; + + /** + * @dev Returns token address on Ronin network. + * Note: Reverts for unsupported token. + */ + function getRoninToken(address _mainchainToken) external view returns (MappedToken memory _token); +} diff --git a/contracts/interfaces/IRoninGatewayV2.sol b/contracts/interfaces/IRoninGatewayV2.sol new file mode 100644 index 000000000..466f1c411 --- /dev/null +++ b/contracts/interfaces/IRoninGatewayV2.sol @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../libraries/Transfer.sol"; +import "./consumers/MappedTokenConsumer.sol"; + +interface IRoninGatewayV2 is MappedTokenConsumer { + /// @dev Emitted when the assets are depositted + event Deposited(bytes32 receiptHash, Transfer.Receipt receipt); + /// @dev Emitted when the withdrawal is requested + event WithdrawalRequested(bytes32 receiptHash, Transfer.Receipt); + /// @dev Emitted when the assets are withdrawn on mainchain + event MainchainWithdrew(bytes32 receiptHash, Transfer.Receipt receipt); + /// @dev Emitted when the withdrawal signatures is requested + event WithdrawalSignaturesRequested(bytes32 receiptHash, Transfer.Receipt); + /// @dev Emitted when the tokens are mapped + event TokenMapped(address[] roninTokens, address[] mainchainTokens, uint256[] chainIds, Token.Standard[] standards); + + /** + * @dev Returns withdrawal count. + */ + function withdrawalCount() external view returns (uint256); + + /** + * @dev Returns withdrawal signatures. + */ + function getWithdrawalSignatures(uint256 _withdrawalId, address[] calldata _validators) + external + view + returns (bytes[] memory); + + /** + * @dev Deposits based on the receipt. + * + * Requirements: + * - The method caller is a validator. + * + * Emits the `Deposited` once the assets are released. + * + * @notice The assets will be transferred whenever the valid call passes the quorum threshold. + * + */ + function depositFor(Transfer.Receipt calldata _receipt) external; + + /** + * @dev Marks the withdrawals are done on mainchain and returns the boolean array indicating whether the withdrawal + * vote is already done before. + * + * Requirements: + * - The method caller is a validator. + * + * Emits the `MainchainWithdrew` once the valid call passes the quorum threshold. + * + * @notice Not reverting to avoid unnecessary failed transactions because the validators can send transactions at the + * same time. + * + */ + function tryBulkAcknowledgeMainchainWithdrew(uint256[] calldata _withdrawalIds) external returns (bool[] memory); + + /** + * @dev Tries bulk deposits based on the receipts and returns the boolean array indicating whether the deposit vote + * is already done before. Reverts if the deposit is invalid or is voted by the validator again. + * + * Requirements: + * - The method caller is a validator. + * + * Emits the `Deposited` once the assets are released. + * + * @notice The assets will be transferred whenever the valid call for the receipt passes the quorum threshold. Not + * reverting to avoid unnecessary failed transactions because the validators can send transactions at the same time. + * + */ + function tryBulkDepositFor(Transfer.Receipt[] calldata _receipts) external returns (bool[] memory); + + /** + * @dev Locks the assets and request withdrawal. + * + * Emits the `WithdrawalRequested` event. + * + */ + function requestWithdrawalFor(Transfer.Request calldata _request, uint256 _chainId) external; + + /** + * @dev Bulk requests withdrawals. + * + * Emits the `WithdrawalRequested` events. + * + */ + function bulkRequestWithdrawalFor(Transfer.Request[] calldata _requests, uint256 _chainId) external; + + /** + * @dev Requests withdrawal signatures for a specific withdrawal. + * + * Emits the `WithdrawalSignaturesRequested` event. + * + */ + function requestWithdrawalSignatures(uint256 _withdrawalId) external; + + /** + * @dev Submits withdrawal signatures. + * + * Requirements: + * - The method caller is a validator. + * + */ + function bulkSubmitWithdrawalSignatures(uint256[] calldata _withdrawals, bytes[] calldata _signatures) external; + + /** + * @dev Maps Ronin tokens to mainchain networks. + * + * Requirement: + * - The method caller is admin. + * - The arrays have the same length and its length larger than 0. + * + * Emits the `TokenMapped` event. + * + */ + function mapTokens( + address[] calldata _roninTokens, + address[] calldata _mainchainTokens, + uint256[] calldata chainIds, + Token.Standard[] calldata _standards + ) external; + + /** + * @dev Returns whether the deposit is casted by the voter. + */ + function depositVoted( + uint256 _chainId, + uint256 _depositId, + address _voter + ) external view returns (bool); + + /** + * @dev Returns whether the mainchain withdrew is casted by the voter. + */ + function mainchainWithdrewVoted(uint256 _withdrawalId, address _voter) external view returns (bool); + + /** + * @dev Returns whether the withdrawal is done on mainchain. + */ + function mainchainWithdrew(uint256 _withdrawalId) external view returns (bool); + + /** + * @dev Returns mainchain token address. + * Reverts for unsupported token. + */ + function getMainchainToken(address _roninToken, uint256 _chainId) external view returns (MappedToken memory _token); +} diff --git a/contracts/interfaces/IRoninGovernanceAdmin.sol b/contracts/interfaces/IRoninGovernanceAdmin.sol new file mode 100644 index 000000000..01fb57581 --- /dev/null +++ b/contracts/interfaces/IRoninGovernanceAdmin.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IRoninGovernanceAdmin { + /** + * @dev Returns the last voted block of the bridge voter. + */ + function lastVotedBlock(address _bridgeVoter) external view returns (uint256); +} diff --git a/contracts/interfaces/IRoninTrustedOrganization.sol b/contracts/interfaces/IRoninTrustedOrganization.sol index 6cca8ee3b..9a9935263 100644 --- a/contracts/interfaces/IRoninTrustedOrganization.sol +++ b/contracts/interfaces/IRoninTrustedOrganization.sol @@ -2,16 +2,28 @@ pragma solidity ^0.8.9; -import "./consumers/WeightedAddressConsumer.sol"; import "./IQuorum.sol"; -interface IRoninTrustedOrganization is WeightedAddressConsumer, IQuorum { +interface IRoninTrustedOrganization is IQuorum { + struct TrustedOrganization { + // Address of the validator that produces block, e.g. block.coinbase. This is so-called validator address. + address consensusAddr; + // Address to voting proposal + address governor; + // Address to voting bridge operators + address bridgeVoter; + // Its Weight + uint256 weight; + // The block that the organization was added + uint256 addedBlock; + } + /// @dev Emitted when the trusted organization is added. - event TrustedOrganizationAdded(WeightedAddress org); + event TrustedOrganizationsAdded(TrustedOrganization[] orgs); /// @dev Emitted when the trusted organization is updated. - event TrustedOrganizationUpdated(WeightedAddress org); + event TrustedOrganizationsUpdated(TrustedOrganization[] orgs); /// @dev Emitted when the trusted organization is removed. - event TrustedOrganizationRemoved(address org); + event TrustedOrganizationsRemoved(address[] orgs); /** * @dev Adds a list of addresses into the trusted organization. @@ -19,11 +31,12 @@ interface IRoninTrustedOrganization is WeightedAddressConsumer, IQuorum { * Requirements: * - The weights should larger than 0. * - The method caller is admin. + * - The field `addedBlock` should be blank. * * Emits the event `TrustedOrganizationAdded` once an organization is added. * */ - function addTrustedOrganizations(WeightedAddress[] calldata) external; + function addTrustedOrganizations(TrustedOrganization[] calldata) external; /** * @dev Updates weights for a list of existent trusted organization. @@ -35,7 +48,7 @@ interface IRoninTrustedOrganization is WeightedAddressConsumer, IQuorum { * Emits the `TrustedOrganizationUpdated` event. * */ - function updateTrustedOrganizations(WeightedAddress[] calldata _list) external; + function updateTrustedOrganizations(TrustedOrganization[] calldata _list) external; /** * @dev Removes a list of addresses from the trusted organization. @@ -54,24 +67,54 @@ interface IRoninTrustedOrganization is WeightedAddressConsumer, IQuorum { function totalWeights() external view returns (uint256); /** - * @dev Returns the weight of an address. + * @dev Returns the weight of a consensus. + */ + function getConsensusWeight(address _consensusAddr) external view returns (uint256); + + /** + * @dev Returns the weight of a governor. + */ + function getGovernorWeight(address _governor) external view returns (uint256); + + /** + * @dev Returns the weight of a bridge voter. + */ + function getBridgeVoterWeight(address _addr) external view returns (uint256); + + /** + * @dev Returns the weights of a list of consensus addresses. */ - function getWeight(address _addr) external view returns (uint256); + function getConsensusWeights(address[] calldata _list) external view returns (uint256[] memory); /** - * @dev Returns the weights of a list of addresses. + * @dev Returns the weights of a list of governor addresses. */ - function getWeights(address[] calldata _list) external view returns (uint256[] memory); + function getGovernorWeights(address[] calldata _list) external view returns (uint256[] memory); /** - * @dev Returns total weights of the address list. + * @dev Returns the weights of a list of bridge voter addresses. */ - function sumWeights(address[] calldata _list) external view returns (uint256 _res); + function getBridgeVoterWeights(address[] calldata _list) external view returns (uint256[] memory); + + /** + * @dev Returns total weights of the consensus list. + */ + function sumConsensusWeights(address[] calldata _list) external view returns (uint256 _res); + + /** + * @dev Returns total weights of the governor list. + */ + function sumGovernorWeights(address[] calldata _list) external view returns (uint256 _res); + + /** + * @dev Returns total weights of the bridge voter list. + */ + function sumBridgeVoterWeights(address[] calldata _list) external view returns (uint256 _res); /** * @dev Returns the trusted organization at `_index`. */ - function getTrustedOrganizationAt(uint256 _index) external view returns (WeightedAddress memory); + function getTrustedOrganizationAt(uint256 _index) external view returns (TrustedOrganization memory); /** * @dev Returns the number of trusted organizations. @@ -79,7 +122,14 @@ interface IRoninTrustedOrganization is WeightedAddressConsumer, IQuorum { function countTrustedOrganizations() external view returns (uint256); /** - * @dev Returns all of the trusted organization addresses. + * @dev Returns all of the trusted organizations. + */ + function getAllTrustedOrganizations() external view returns (TrustedOrganization[] memory); + + /** + * @dev Returns the trusted organization by consensus address. + * + * Reverts once the consensus address is non-existent. */ - function getAllTrustedOrganizations() external view returns (WeightedAddress[] memory); + function getTrustedOrganization(address _consensusAddr) external view returns (TrustedOrganization memory); } diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index c60e9055f..62897f876 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -7,13 +7,18 @@ interface ISlashIndicator { UNKNOWN, MISDEMEANOR, FELONY, - DOUBLE_SIGNING + DOUBLE_SIGNING, + BRIDGE_VOTING } /// @dev Emitted when the validator is slashed for unavailability event UnavailabilitySlashed(address indexed validator, SlashType slashType, uint256 period); /// @dev Emitted when the thresholds updated event SlashThresholdsUpdated(uint256 felonyThreshold, uint256 misdemeanorThreshold); + /// @dev Emitted when the threshold to slash when trusted organization does not vote for bridge operators is updated + event BridgeVotingThresholdUpdated(uint256 threshold); + /// @dev Emitted when the amount of RON to slash bridge voting is updated + event BridgeVotingSlashAmountUpdated(uint256 amount); /// @dev Emitted when the amount of slashing felony updated event SlashFelonyAmountUpdated(uint256 slashFelonyAmount); /// @dev Emitted when the amount of slashing double sign updated @@ -51,6 +56,13 @@ interface ISlashIndicator { bytes calldata _header2 ) external; + /** + * @dev Slashes for bridge voter governance. + * + * Emits the event `UnavailabilitySlashed`. + */ + function slashBridgeVoting(address _consensusAddr) external; + /** * @dev Sets the slash thresholds * @@ -95,6 +107,28 @@ interface ISlashIndicator { */ function setFelonyJailDuration(uint256 _felonyJailDuration) external; + /** + * @dev Sets the threshold to slash when trusted organization does not vote for bridge operators. + * + * Requirements: + * - Only admin can call this method + * + * Emits the event `BridgeVotingThresholdUpdated` + * + */ + function setBridgeVotingThreshold(uint256 _threshold) external; + + /** + * @dev Sets the amount of RON to slash bridge voting. + * + * Requirements: + * - Only admin can call this method + * + * Emits the event `BridgeVotingSlashAmountUpdated` + * + */ + function setBridgeVotingSlashAmount(uint256 _amount) external; + /** * @dev Returns the current unavailability indicator of a validator. */ diff --git a/contracts/interfaces/IWETH.sol b/contracts/interfaces/IWETH.sol new file mode 100644 index 000000000..448714338 --- /dev/null +++ b/contracts/interfaces/IWETH.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IWETH { + function deposit() external payable; + + function withdraw(uint256 _wad) external; + + function balanceOf(address) external view returns (uint256); +} diff --git a/contracts/interfaces/collections/IHasRoninGovernanceAdminContract.sol b/contracts/interfaces/collections/IHasRoninGovernanceAdminContract.sol new file mode 100644 index 000000000..905cf66c9 --- /dev/null +++ b/contracts/interfaces/collections/IHasRoninGovernanceAdminContract.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IHasRoninGovernanceAdminContract { + /// @dev Emitted when the ronin governance admin contract is updated. + event RoninGovernanceAdminContractUpdated(address); + + /** + * @dev Returns the ronin governance admin contract. + */ + function roninGovernanceAdminContract() external view returns (address); + + /** + * @dev Sets the ronin governance admin contract. + * + * Requirements: + * - The method caller is admin. + * - The new address is a contract. + * + * Emits the event `RoninGovernanceAdminContractUpdated`. + * + */ + function setRoninGovernanceAdminContract(address) external; +} diff --git a/contracts/interfaces/consumers/MappedTokenConsumer.sol b/contracts/interfaces/consumers/MappedTokenConsumer.sol new file mode 100644 index 000000000..ca0f57eba --- /dev/null +++ b/contracts/interfaces/consumers/MappedTokenConsumer.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../../libraries/Token.sol"; + +interface MappedTokenConsumer { + struct MappedToken { + Token.Standard erc; + address tokenAddr; + } +} diff --git a/contracts/libraries/BridgeOperatorsBallot.sol b/contracts/libraries/BridgeOperatorsBallot.sol index 0c6bbb3a7..2b033f4a3 100644 --- a/contracts/libraries/BridgeOperatorsBallot.sol +++ b/contracts/libraries/BridgeOperatorsBallot.sol @@ -5,38 +5,19 @@ import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "../interfaces/consumers/WeightedAddressConsumer.sol"; library BridgeOperatorsBallot { - // keccak256("BridgeOperator(address addr,uint256 weight)"); - bytes32 public constant BRIDGE_OPERATOR_TYPEHASH = 0xe71132f1797176c8456299d5325989bbf16523f1e2e3aef4554d23f982955a2c; - - /** - * @dev Returns hash of an operator struct. - */ - function hash(WeightedAddressConsumer.WeightedAddress calldata _operator) internal pure returns (bytes32) { - return keccak256(abi.encode(BRIDGE_OPERATOR_TYPEHASH, _operator.addr, _operator.weight)); - } - - // keccak256("BridgeOperatorsBallot(uint256 period,BridgeOperator[] operators)BridgeOperator(address addr,uint256 weight)"); - bytes32 public constant BRIDGE_OPERATORS_ACKNOWLEDGE_BALLOT_TYPEHASH = - 0x086d287088869477577720f66bf2a8412510e726fd1a893739cf6c2280aadcb5; + // keccak256("BridgeOperatorsBallot(uint256 period,address[] operators)"); + bytes32 public constant BRIDGE_OPERATORS_BALLOT_TYPEHASH = + 0xeea5e3908ac28cbdbbce8853e49444c558a0a03597e98ef19e6ff86162ed9ae3; /** * @dev Returns hash of the ballot. */ - function hash(uint256 _period, WeightedAddressConsumer.WeightedAddress[] calldata _operators) - internal - pure - returns (bytes32) - { - bytes32[] memory _hashArr = new bytes32[](_operators.length); - for (uint256 _i; _i < _hashArr.length; _i++) { - _hashArr[_i] = hash(_operators[_i]); - } - + function hash(uint256 _period, address[] memory _operators) internal pure returns (bytes32) { bytes32 _operatorsHash; assembly { - _operatorsHash := keccak256(add(_hashArr, 32), mul(mload(_hashArr), 32)) + _operatorsHash := keccak256(add(_operators, 32), mul(mload(_operators), 32)) } - return keccak256(abi.encode(BRIDGE_OPERATORS_ACKNOWLEDGE_BALLOT_TYPEHASH, _period, _operatorsHash)); + return keccak256(abi.encode(BRIDGE_OPERATORS_BALLOT_TYPEHASH, _period, _operatorsHash)); } } diff --git a/contracts/libraries/Token.sol b/contracts/libraries/Token.sol new file mode 100644 index 000000000..829fa578f --- /dev/null +++ b/contracts/libraries/Token.sol @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import "../interfaces/IWETH.sol"; + +library Token { + enum Standard { + ERC20, + ERC721 + } + struct Info { + Standard erc; + // For ERC20: the id must be 0 and the quantity is larger than 0. + // For ERC721: the quantity must be 0. + uint256 id; + uint256 quantity; + } + + // keccak256("TokenInfo(uint8 erc,uint256 id,uint256 quantity)"); + bytes32 public constant INFO_TYPE_HASH = 0x1e2b74b2a792d5c0f0b6e59b037fa9d43d84fbb759337f0112fcc15ca414fc8d; + + /** + * @dev Returns token info struct hash. + */ + function hash(Info memory _info) internal pure returns (bytes32) { + return keccak256(abi.encode(INFO_TYPE_HASH, _info.erc, _info.id, _info.quantity)); + } + + /** + * @dev Validates the token info. + */ + function validate(Info memory _info) internal pure { + require( + (_info.erc == Standard.ERC20 && _info.quantity > 0 && _info.id == 0) || + (_info.erc == Standard.ERC721 && _info.quantity == 0), + "Token: invalid info" + ); + } + + /** + * @dev Transfer asset from. + * + * Requirements: + * - The `_from` address must approve for the contract using this library. + * + */ + function transferFrom( + Info memory _info, + address _from, + address _to, + address _token + ) internal { + bool _success; + bytes memory _data; + if (_info.erc == Standard.ERC20) { + (_success, _data) = _token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, _from, _to, _info.quantity)); + _success = _success && (_data.length == 0 || abi.decode(_data, (bool))); + } else if (_info.erc == Standard.ERC721) { + // bytes4(keccak256("transferFrom(address,address,uint256)")) + (_success, ) = _token.call(abi.encodeWithSelector(0x23b872dd, _from, _to, _info.id)); + } else { + revert("Token: unsupported token standard"); + } + + if (!_success) { + revert( + string( + abi.encodePacked( + "Token: could not transfer ", + toString(_info), + " from ", + Strings.toHexString(uint160(_from), 20), + " to ", + Strings.toHexString(uint160(_to), 20), + " token ", + Strings.toHexString(uint160(_token), 20) + ) + ) + ); + } + } + + /** + * @dev Transfers ERC721 token and returns the result. + */ + function tryTransferERC721( + address _token, + address _to, + uint256 _id + ) internal returns (bool _success) { + (_success, ) = _token.call(abi.encodeWithSelector(IERC721.transferFrom.selector, address(this), _to, _id)); + } + + /** + * @dev Transfers ERC20 token and returns the result. + */ + function tryTransferERC20( + address _token, + address _to, + uint256 _quantity + ) internal returns (bool _success) { + bytes memory _data; + (_success, _data) = _token.call(abi.encodeWithSelector(IERC20.transfer.selector, _to, _quantity)); + _success = _success && (_data.length == 0 || abi.decode(_data, (bool))); + } + + /** + * @dev Transfer assets from current address to `_to` address. + */ + function transfer( + Info memory _info, + address _to, + address _token + ) internal { + bool _success; + if (_info.erc == Standard.ERC20) { + _success = tryTransferERC20(_token, _to, _info.quantity); + } else if (_info.erc == Standard.ERC721) { + _success = tryTransferERC721(_token, _to, _info.id); + } else { + revert("Token: unsupported token standard"); + } + + if (!_success) { + revert( + string( + abi.encodePacked( + "Token: could not transfer ", + toString(_info), + " to ", + Strings.toHexString(uint160(_to), 20), + " token ", + Strings.toHexString(uint160(_token), 20) + ) + ) + ); + } + } + + /** + * @dev Tries minting and transfering assets. + * + * @notice Prioritizes transfer native token if the token is wrapped. + * + */ + function handleAssetTransfer( + Info memory _info, + address payable _to, + address _token, + IWETH _wrappedNativeToken + ) internal { + bool _success; + if (_token == address(_wrappedNativeToken)) { + // Try sending the native token before transferring the wrapped token + if (!_to.send(_info.quantity)) { + _wrappedNativeToken.deposit{ value: _info.quantity }(); + transfer(_info, _to, _token); + } + } else if (_info.erc == Token.Standard.ERC20) { + uint256 _balance = IERC20(_token).balanceOf(address(this)); + + if (_balance < _info.quantity) { + // bytes4(keccak256("mint(address,uint256)")) + (_success, ) = _token.call(abi.encodeWithSelector(0x40c10f19, address(this), _info.quantity - _balance)); + require(_success, "Token: ERC20 minting failed"); + } + + transfer(_info, _to, _token); + } else if (_info.erc == Token.Standard.ERC721) { + if (!tryTransferERC721(_token, _to, _info.id)) { + // bytes4(keccak256("mint(address,uint256)")) + (_success, ) = _token.call(abi.encodeWithSelector(0x40c10f19, _to, _info.id)); + require(_success, "Token: ERC721 minting failed"); + } + } else { + revert("Token: unsupported token standard"); + } + } + + /** + * @dev Returns readable string. + */ + function toString(Info memory _info) internal pure returns (string memory) { + return + string( + abi.encodePacked( + "TokenInfo(", + Strings.toHexString(uint160(_info.erc), 1), + ",", + Strings.toHexString(_info.id), + ",", + Strings.toHexString(_info.quantity), + ")" + ) + ); + } + + struct Owner { + address addr; + address tokenAddr; + uint256 chainId; + } + + // keccak256("TokenOwner(address addr,address tokenAddr,uint256 chainId)"); + bytes32 public constant OWNER_TYPE_HASH = 0x353bdd8d69b9e3185b3972e08b03845c0c14a21a390215302776a7a34b0e8764; + + /** + * @dev Returns ownership struct hash. + */ + function hash(Owner memory _owner) internal pure returns (bytes32) { + return keccak256(abi.encode(OWNER_TYPE_HASH, _owner.addr, _owner.tokenAddr, _owner.chainId)); + } +} diff --git a/contracts/libraries/Transfer.sol b/contracts/libraries/Transfer.sol new file mode 100644 index 000000000..54b768731 --- /dev/null +++ b/contracts/libraries/Transfer.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import "./Token.sol"; + +library Transfer { + using ECDSA for bytes32; + + enum Kind { + Deposit, + Withdrawal + } + + struct Request { + // For deposit request: Recipient address on Ronin network + // For withdrawal request: Recipient address on mainchain network + address recipientAddr; + // Token address to deposit/withdraw + // Value 0: native token + address tokenAddr; + Token.Info info; + } + + /** + * @dev Converts the transfer request into the deposit receipt. + */ + function into_deposit_receipt( + Request memory _request, + address _requester, + uint256 _id, + address _roninTokenAddr, + uint256 _roninChainId + ) internal view returns (Receipt memory _receipt) { + _receipt.id = _id; + _receipt.kind = Kind.Deposit; + _receipt.mainchain.addr = _requester; + _receipt.mainchain.tokenAddr = _request.tokenAddr; + _receipt.mainchain.chainId = block.chainid; + _receipt.ronin.addr = _request.recipientAddr; + _receipt.ronin.tokenAddr = _roninTokenAddr; + _receipt.ronin.chainId = _roninChainId; + _receipt.info = _request.info; + } + + /** + * @dev Converts the transfer request into the withdrawal receipt. + */ + function into_withdrawal_receipt( + Request memory _request, + address _requester, + uint256 _id, + address _mainchainTokenAddr, + uint256 _mainchainId + ) internal view returns (Receipt memory _receipt) { + _receipt.id = _id; + _receipt.kind = Kind.Withdrawal; + _receipt.ronin.addr = _requester; + _receipt.ronin.tokenAddr = _request.tokenAddr; + _receipt.ronin.chainId = block.chainid; + _receipt.mainchain.addr = _request.recipientAddr; + _receipt.mainchain.tokenAddr = _mainchainTokenAddr; + _receipt.mainchain.chainId = _mainchainId; + _receipt.info = _request.info; + } + + struct Receipt { + uint256 id; + Kind kind; + Token.Owner mainchain; + Token.Owner ronin; + Token.Info info; + } + + // keccak256("Receipt(uint256 id,uint8 kind,TokenOwner mainchain,TokenOwner ronin,TokenInfo info)TokenInfo(uint8 erc,uint256 id,uint256 quantity)TokenOwner(address addr,address tokenAddr,uint256 chainId)"); + bytes32 public constant TYPE_HASH = 0xb9d1fe7c9deeec5dc90a2f47ff1684239519f2545b2228d3d91fb27df3189eea; + + /** + * @dev Returns token info struct hash. + */ + function hash(Receipt memory _receipt) internal pure returns (bytes32) { + return + keccak256( + abi.encode( + TYPE_HASH, + _receipt.id, + _receipt.kind, + Token.hash(_receipt.mainchain), + Token.hash(_receipt.ronin), + Token.hash(_receipt.info) + ) + ); + } + + /** + * @dev Returns the receipt digest. + */ + function receiptDigest(bytes32 _domainSeparator, bytes32 _receiptHash) internal pure returns (bytes32) { + return _domainSeparator.toTypedDataHash(_receiptHash); + } +} diff --git a/contracts/mainchain/MainchainGatewayV2.sol b/contracts/mainchain/MainchainGatewayV2.sol new file mode 100644 index 000000000..0813abc7e --- /dev/null +++ b/contracts/mainchain/MainchainGatewayV2.sol @@ -0,0 +1,453 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "../extensions/GatewayV2.sol"; +import "../extensions/WithdrawalLimitation.sol"; +import "../libraries/Transfer.sol"; +import "../interfaces/IMainchainGatewayV2.sol"; + +contract MainchainGatewayV2 is WithdrawalLimitation, Initializable, AccessControlEnumerable, IMainchainGatewayV2 { + using Token for Token.Info; + using Transfer for Transfer.Request; + using Transfer for Transfer.Receipt; + + /// @dev Emitted when the bridge operators are replaced + event BridgeOperatorsReplaced(address[] operators); + + /// @dev Withdrawal unlocker role hash + bytes32 public constant WITHDRAWAL_UNLOCKER_ROLE = keccak256("WITHDRAWAL_UNLOCKER_ROLE"); + + /// @dev Wrapped native token address + IWETH public wrappedNativeToken; + /// @dev Ronin network id + uint256 public roninChainId; + /// @dev Total deposit + uint256 public depositCount; + /// @dev Domain seperator + bytes32 internal _domainSeparator; + /// @dev Mapping from mainchain token => token address on Ronin network + mapping(address => MappedToken) internal _roninToken; + /// @dev Mapping from withdrawal id => withdrawal hash + mapping(uint256 => bytes32) public withdrawalHash; + /// @dev Mapping from withdrawal id => locked + mapping(uint256 => bool) public withdrawalLocked; + + /// @dev Mapping from validator address => last block that the bridge operator is added + mapping(address => uint256) internal _bridgeOperatorAddedBlock; + /// @dev Bridge operators array + address[] internal _bridgeOperators; + + fallback() external payable { + _fallback(); + } + + receive() external payable { + _fallback(); + } + + /** + * @dev Initializes contract storage. + */ + function initialize( + address _roleSetter, + IWETH _wrappedToken, + uint256 _roninChainId, + uint256 _numerator, + uint256 _highTierVWNumerator, + uint256 _denominator, + // _addresses[0]: mainchainTokens + // _addresses[1]: roninTokens + // _addresses[2]: withdrawalUnlockers + address[][3] calldata _addresses, + // _thresholds[0]: highTierThreshold + // _thresholds[1]: lockedThreshold + // _thresholds[2]: unlockFeePercentages + // _thresholds[3]: dailyWithdrawalLimit + uint256[][4] calldata _thresholds, + Token.Standard[] calldata _standards + ) external payable virtual initializer { + _setupRole(DEFAULT_ADMIN_ROLE, _roleSetter); + roninChainId = _roninChainId; + + _setWrappedNativeTokenContract(_wrappedToken); + _updateDomainSeparator(); + _setThreshold(_numerator, _denominator); + _setHighTierVoteWeightThreshold(_highTierVWNumerator, _denominator); + _verifyThresholds(); + + if (_addresses[0].length > 0) { + // Map mainchain tokens to ronin tokens + _mapTokens(_addresses[0], _addresses[1], _standards); + // Sets thresholds based on the mainchain tokens + _setHighTierThresholds(_addresses[0], _thresholds[0]); + _setLockedThresholds(_addresses[0], _thresholds[1]); + _setUnlockFeePercentages(_addresses[0], _thresholds[2]); + _setDailyWithdrawalLimits(_addresses[0], _thresholds[3]); + } + + // Grant role for withdrawal unlocker + for (uint256 _i; _i < _addresses[2].length; _i++) { + _grantRole(WITHDRAWAL_UNLOCKER_ROLE, _addresses[2][_i]); + } + } + + /** + * @dev See {IBridge-replaceBridgeOperators}. + */ + function replaceBridgeOperators(address[] calldata _list) external onlyAdmin { + address _addr; + for (uint256 _i = 0; _i < _list.length; _i++) { + _addr = _list[_i]; + if (_bridgeOperatorAddedBlock[_addr] == 0) { + _bridgeOperators.push(_addr); + } + _bridgeOperatorAddedBlock[_addr] = block.number; + } + + { + uint256 _i; + while (_i < _bridgeOperators.length) { + _addr = _bridgeOperators[_i]; + if (_bridgeOperatorAddedBlock[_addr] < block.number) { + delete _bridgeOperatorAddedBlock[_addr]; + _bridgeOperators[_i] = _bridgeOperators[_bridgeOperators.length - 1]; + _bridgeOperators.pop(); + continue; + } + _i++; + } + } + + emit BridgeOperatorsReplaced(_list); + } + + /** + * @dev See {IBridge-getBridgeOperators}. + */ + function getBridgeOperators() external view returns (address[] memory) { + return _bridgeOperators; + } + + /** + * @dev Receives ether without doing anything. Use this function to topup native token. + */ + function receiveEther() external payable {} + + /** + * @dev See {IMainchainGatewayV2-DOMAIN_SEPARATOR}. + */ + function DOMAIN_SEPARATOR() external view virtual returns (bytes32) { + return _domainSeparator; + } + + /** + * @dev See {IMainchainGatewayV2-setWrappedNativeTokenContract}. + */ + function setWrappedNativeTokenContract(IWETH _wrappedToken) external virtual onlyAdmin { + _setWrappedNativeTokenContract(_wrappedToken); + } + + /** + * @dev See {IMainchainGatewayV2-requestDepositFor}. + */ + function requestDepositFor(Transfer.Request calldata _request) external payable virtual whenNotPaused { + _requestDepositFor(_request, msg.sender); + } + + /** + * @dev See {IMainchainGatewayV2-submitWithdrawal}. + */ + function submitWithdrawal(Transfer.Receipt calldata _receipt, Signature[] calldata _signatures) + external + virtual + whenNotPaused + returns (bool _locked) + { + return _submitWithdrawal(_receipt, _signatures); + } + + /** + * @dev See {IMainchainGatewayV2-unlockWithdrawal}. + */ + function unlockWithdrawal(Transfer.Receipt calldata _receipt) external onlyRole(WITHDRAWAL_UNLOCKER_ROLE) { + bytes32 _receiptHash = _receipt.hash(); + require(withdrawalHash[_receipt.id] == _receipt.hash(), "MainchainGatewayV2: invalid receipt"); + require(withdrawalLocked[_receipt.id], "MainchainGatewayV2: query for approved withdrawal"); + delete withdrawalLocked[_receipt.id]; + emit WithdrawalUnlocked(_receiptHash, _receipt); + + address _token = _receipt.mainchain.tokenAddr; + if (_receipt.info.erc == Token.Standard.ERC20) { + Token.Info memory _feeInfo = _receipt.info; + _feeInfo.quantity = _computeFeePercentage(_receipt.info.quantity, unlockFeePercentages[_token]); + Token.Info memory _withdrawInfo = _receipt.info; + _withdrawInfo.quantity = _receipt.info.quantity - _feeInfo.quantity; + + _feeInfo.handleAssetTransfer(payable(msg.sender), _token, wrappedNativeToken); + _withdrawInfo.handleAssetTransfer(payable(_receipt.mainchain.addr), _token, wrappedNativeToken); + } else { + _receipt.info.handleAssetTransfer(payable(_receipt.mainchain.addr), _token, wrappedNativeToken); + } + + emit Withdrew(_receiptHash, _receipt); + } + + /** + * @dev See {IMainchainGatewayV2-mapTokens}. + */ + function mapTokens( + address[] calldata _mainchainTokens, + address[] calldata _roninTokens, + Token.Standard[] calldata _standards + ) external virtual onlyAdmin { + require(_mainchainTokens.length > 0, "MainchainGatewayV2: query for empty array"); + _mapTokens(_mainchainTokens, _roninTokens, _standards); + } + + /** + * @dev See {IMainchainGatewayV2-mapTokensAndThresholds}. + */ + function mapTokensAndThresholds( + address[] calldata _mainchainTokens, + address[] calldata _roninTokens, + Token.Standard[] calldata _standards, + // _thresholds[0]: highTierThreshold + // _thresholds[1]: lockedThreshold + // _thresholds[2]: unlockFeePercentages + // _thresholds[3]: dailyWithdrawalLimit + uint256[][4] calldata _thresholds + ) external virtual onlyAdmin { + require(_mainchainTokens.length > 0, "MainchainGatewayV2: query for empty array"); + _mapTokens(_mainchainTokens, _roninTokens, _standards); + _setHighTierThresholds(_mainchainTokens, _thresholds[0]); + _setLockedThresholds(_mainchainTokens, _thresholds[1]); + _setUnlockFeePercentages(_mainchainTokens, _thresholds[2]); + _setDailyWithdrawalLimits(_mainchainTokens, _thresholds[3]); + } + + /** + * @dev See {IMainchainGatewayV2-getRoninToken}. + */ + function getRoninToken(address _mainchainToken) public view returns (MappedToken memory _token) { + _token = _roninToken[_mainchainToken]; + require(_token.tokenAddr != address(0), "MainchainGatewayV2: unsupported token"); + } + + /** + * @dev Maps mainchain tokens to Ronin network. + * + * Requirement: + * - The arrays have the same length. + * + * Emits the `TokenMapped` event. + * + */ + function _mapTokens( + address[] calldata _mainchainTokens, + address[] calldata _roninTokens, + Token.Standard[] calldata _standards + ) internal virtual { + require( + _mainchainTokens.length == _roninTokens.length && _mainchainTokens.length == _standards.length, + "MainchainGatewayV2: invalid array length" + ); + + for (uint256 _i; _i < _mainchainTokens.length; _i++) { + _roninToken[_mainchainTokens[_i]].tokenAddr = _roninTokens[_i]; + _roninToken[_mainchainTokens[_i]].erc = _standards[_i]; + } + + emit TokenMapped(_mainchainTokens, _roninTokens, _standards); + } + + /** + * @dev Submits withdrawal receipt. + * + * Requirements: + * - The receipt kind is withdrawal. + * - The receipt is to withdraw on this chain. + * - The receipt is not used to withdraw before. + * - The withdrawal is not reached the limit threshold. + * - The signer weight total is larger than or equal to the minimum threshold. + * - The signature signers are in order. + * + * Emits the `Withdrew` once the assets are released. + * + */ + function _submitWithdrawal(Transfer.Receipt calldata _receipt, Signature[] memory _signatures) + internal + virtual + returns (bool _locked) + { + uint256 _id = _receipt.id; + uint256 _quantity = _receipt.info.quantity; + address _tokenAddr = _receipt.mainchain.tokenAddr; + + _receipt.info.validate(); + require(_receipt.kind == Transfer.Kind.Withdrawal, "MainchainGatewayV2: invalid receipt kind"); + require(_receipt.mainchain.chainId == block.chainid, "MainchainGatewayV2: invalid chain id"); + MappedToken memory _token = getRoninToken(_receipt.mainchain.tokenAddr); + require( + _token.erc == _receipt.info.erc && _token.tokenAddr == _receipt.ronin.tokenAddr, + "MainchainGatewayV2: invalid receipt" + ); + require(withdrawalHash[_id] == bytes32(0), "MainchainGatewayV2: query for processed withdrawal"); + require( + _receipt.info.erc == Token.Standard.ERC721 || !_reachedWithdrawalLimit(_tokenAddr, _quantity), + "MainchainGatewayV2: reached daily withdrawal limit" + ); + + bytes32 _receiptHash = _receipt.hash(); + bytes32 _receiptDigest = Transfer.receiptDigest(_domainSeparator, _receiptHash); + + uint256 _minimumVoteWeight; + (_minimumVoteWeight, _locked) = _computeMinVoteWeight(_receipt.info.erc, _tokenAddr, _quantity); + + { + bool _passed; + address _signer; + address _lastSigner; + Signature memory _sig; + uint256 _weight; + for (uint256 _i; _i < _signatures.length; _i++) { + _sig = _signatures[_i]; + _signer = ecrecover(_receiptDigest, _sig.v, _sig.r, _sig.s); + require(_lastSigner < _signer, "MainchainGatewayV2: invalid order"); + _lastSigner = _signer; + + _weight += _getWeight(_signer); + if (_weight >= _minimumVoteWeight) { + _passed = true; + break; + } + } + require(_passed, "MainchainGatewayV2: query for insufficient vote weight"); + withdrawalHash[_id] = _receiptHash; + } + + if (_locked) { + withdrawalLocked[_id] = true; + emit WithdrawalLocked(_receiptHash, _receipt); + return _locked; + } + + _recordWithdrawal(_tokenAddr, _quantity); + _receipt.info.handleAssetTransfer(payable(_receipt.mainchain.addr), _tokenAddr, wrappedNativeToken); + emit Withdrew(_receiptHash, _receipt); + } + + /** + * @dev Requests deposit made by `_requester` address. + * + * Requirements: + * - The token info is valid. + * - The `msg.value` is 0 while depositing ERC20 token. + * - The `msg.value` is equal to deposit quantity while depositing native token. + * + * Emits the `DepositRequested` event. + * + */ + function _requestDepositFor(Transfer.Request memory _request, address _requester) internal virtual { + MappedToken memory _token; + address _weth = address(wrappedNativeToken); + + _request.info.validate(); + if (_request.tokenAddr == address(0)) { + require(_request.info.quantity == msg.value, "MainchainGatewayV2: invalid request"); + _token = getRoninToken(_weth); + require(_token.erc == _request.info.erc, "MainchainGatewayV2: invalid token standard"); + _request.tokenAddr = _weth; + } else { + require(msg.value == 0, "MainchainGatewayV2: invalid request"); + _token = getRoninToken(_request.tokenAddr); + require(_token.erc == _request.info.erc, "MainchainGatewayV2: invalid token standard"); + _request.info.transferFrom(_requester, address(this), _request.tokenAddr); + // Withdraw if token is WETH + if (_weth == _request.tokenAddr) { + IWETH(_weth).withdraw(_request.info.quantity); + } + } + + uint256 _depositId = depositCount++; + Transfer.Receipt memory _receipt = _request.into_deposit_receipt( + _requester, + _depositId, + _token.tokenAddr, + roninChainId + ); + + emit DepositRequested(_receipt.hash(), _receipt); + } + + /** + * @dev Returns the minimum vote weight for the token. + */ + function _computeMinVoteWeight( + Token.Standard _erc, + address _token, + uint256 _quantity + ) internal virtual returns (uint256 _weight, bool _locked) { + uint256 _totalWeight = _getTotalWeight(); + _weight = _minimumVoteWeight(_totalWeight); + if (_erc == Token.Standard.ERC20) { + if (highTierThreshold[_token] <= _quantity) { + _weight = _highTierVoteWeight(_totalWeight); + } + _locked = _lockedWithdrawalRequest(_token, _quantity); + } + } + + /** + * @dev Update domain seperator. + */ + function _updateDomainSeparator() internal { + _domainSeparator = keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256("MainchainGatewayV2"), + keccak256("2"), + block.chainid, + address(this) + ) + ); + } + + /** + * @dev Sets the WETH contract. + * + * Emits the `WrappedNativeTokenContractUpdated` event. + * + */ + function _setWrappedNativeTokenContract(IWETH _wrapedToken) internal { + wrappedNativeToken = _wrapedToken; + emit WrappedNativeTokenContractUpdated(_wrapedToken); + } + + /** + * @dev Receives ETH from WETH or creates deposit request. + */ + function _fallback() internal virtual whenNotPaused { + if (msg.sender != address(wrappedNativeToken)) { + Transfer.Request memory _request; + _request.recipientAddr = msg.sender; + _request.info.quantity = msg.value; + _requestDepositFor(_request, _request.recipientAddr); + } + } + + /** + * @inheritdoc GatewayV2 + */ + function _getTotalWeight() internal view override returns (uint256) { + return _bridgeOperators.length; + } + + /** + * @dev Returns the weight of an address. + */ + function _getWeight(address _addr) internal view returns (uint256) { + return _bridgeOperatorAddedBlock[_addr] > 0 ? 1 : 0; + } +} diff --git a/contracts/mainchain/MainchainGovernanceAdmin.sol b/contracts/mainchain/MainchainGovernanceAdmin.sol index 764b5e439..a39a8b5a4 100644 --- a/contracts/mainchain/MainchainGovernanceAdmin.sol +++ b/contracts/mainchain/MainchainGovernanceAdmin.sol @@ -69,7 +69,7 @@ contract MainchainGovernanceAdmin is AccessControlEnumerable, GovernanceRelay, G */ function relayBridgeOperators( uint256 _period, - WeightedAddress[] calldata _operators, + address[] calldata _operators, Signature[] calldata _signatures ) external onlyRole(RELAYER_ROLE) { _relayVotesBySignatures(_operators, _signatures, _period, _getMinimumVoteWeight(), DOMAIN_SEPARATOR); @@ -77,23 +77,32 @@ contract MainchainGovernanceAdmin is AccessControlEnumerable, GovernanceRelay, G } /** - * @dev Override {CoreGovernance-_getWeights}. + * @inheritdoc GovernanceRelay */ - function _getWeights(address[] memory _governors) - internal - view - virtual - override(BOsGovernanceRelay, GovernanceRelay) - returns (uint256) - { + function _sumWeights(address[] memory _governors) internal view virtual override returns (uint256) { (bool _success, bytes memory _returndata) = roninTrustedOrganizationContract().staticcall( abi.encodeWithSelector( // TransparentUpgradeableProxyV2.functionDelegateCall.selector, 0x4bb5274a, - abi.encodeWithSelector(IRoninTrustedOrganization.sumWeights.selector, _governors) + abi.encodeWithSelector(IRoninTrustedOrganization.sumGovernorWeights.selector, _governors) ) ); - require(_success, "GovernanceAdmin: proxy call `sumWeights(address[])` failed"); + require(_success, "MainchainGovernanceAdmin: proxy call `sumGovernorWeights(address[])` failed"); + return abi.decode(_returndata, (uint256)); + } + + /** + * @inheritdoc BOsGovernanceRelay + */ + function _sumBridgeVoterWeights(address[] memory _governors) internal view virtual override returns (uint256) { + (bool _success, bytes memory _returndata) = roninTrustedOrganizationContract().staticcall( + abi.encodeWithSelector( + // TransparentUpgradeableProxyV2.functionDelegateCall.selector, + 0x4bb5274a, + abi.encodeWithSelector(IRoninTrustedOrganization.sumBridgeVoterWeights.selector, _governors) + ) + ); + require(_success, "MainchainGovernanceAdmin: proxy call `sumBridgeVoterWeights(address[])` failed"); return abi.decode(_returndata, (uint256)); } } diff --git a/contracts/mocks/MockBridge.sol b/contracts/mocks/MockBridge.sol index 7e18ae5ae..67ac0bd62 100644 --- a/contracts/mocks/MockBridge.sol +++ b/contracts/mocks/MockBridge.sol @@ -5,9 +5,9 @@ pragma solidity ^0.8.9; import "../interfaces/IBridge.sol"; contract MockBridge is IBridge { - WeightedAddress[] public bridgeOperators; + address[] public bridgeOperators; - function replaceBridgeOperators(WeightedAddress[] calldata _list) external override { + function replaceBridgeOperators(address[] calldata _list) external override { while (bridgeOperators.length > 0) { bridgeOperators.pop(); } @@ -16,7 +16,7 @@ contract MockBridge is IBridge { } } - function getBridgeOperators() external view override returns (WeightedAddress[] memory) { + function getBridgeOperators() external view override returns (address[] memory) { return bridgeOperators; } } diff --git a/contracts/multi-chains/RoninTrustedOrganization.sol b/contracts/multi-chains/RoninTrustedOrganization.sol index d4803c178..744879295 100644 --- a/contracts/multi-chains/RoninTrustedOrganization.sol +++ b/contracts/multi-chains/RoninTrustedOrganization.sol @@ -2,33 +2,45 @@ pragma solidity ^0.8.9; +import "@openzeppelin/contracts/utils/Strings.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "../interfaces/IRoninTrustedOrganization.sol"; import "../extensions/collections/HasProxyAdmin.sol"; contract RoninTrustedOrganization is IRoninTrustedOrganization, HasProxyAdmin, Initializable { - using EnumerableSet for EnumerableSet.AddressSet; - uint256 internal _num; uint256 internal _denom; - uint256 internal _totalWeights; + uint256 internal _totalWeight; uint256 internal _nonce; - /// @dev Address set of the trusted organizations - EnumerableSet.AddressSet internal _orgs; - /// @dev Mapping from trusted organization address => its weight - mapping(address => uint256) internal _weight; + /// @dev Mapping from consensus address => weight + mapping(address => uint256) internal _consensusWeight; + /// @dev Mapping from governor address => weight + mapping(address => uint256) internal _governorWeight; + /// @dev Mapping from bridge voter address => weight + mapping(address => uint256) internal _bridgeVoterWeight; + + /// @dev Mapping from consensus address => added block + mapping(address => uint256) internal _addedBlock; + + /// @dev Consensus array + address[] internal _consensusList; + /// @dev Governors array + address[] internal _governorList; + /// @dev Bridge voters array + address[] internal _bridgeVoterList; /** * @dev Initializes the contract storage. */ function initialize( - WeightedAddress[] calldata _trustedOrgs, + TrustedOrganization[] calldata _trustedOrgs, uint256 __num, uint256 __denom ) external initializer { - _addTrustedOrganizations(_trustedOrgs); + if (_trustedOrgs.length > 0) { + _addTrustedOrganizations(_trustedOrgs); + } _setThreshold(__num, __denom); } @@ -43,14 +55,14 @@ contract RoninTrustedOrganization is IRoninTrustedOrganization, HasProxyAdmin, I * @inheritdoc IQuorum */ function checkThreshold(uint256 _voteWeight) external view virtual returns (bool) { - return _voteWeight * _denom >= _num * _totalWeights; + return _voteWeight * _denom >= _num * _totalWeight; } /** * @inheritdoc IQuorum */ function minimumVoteWeight() external view virtual returns (uint256) { - return (_num * _totalWeights + _denom - 1) / _denom; + return (_num * _totalWeight + _denom - 1) / _denom; } /** @@ -68,115 +80,339 @@ contract RoninTrustedOrganization is IRoninTrustedOrganization, HasProxyAdmin, I /** * @inheritdoc IRoninTrustedOrganization */ - function addTrustedOrganizations(WeightedAddress[] calldata _list) external override onlyAdmin { + function addTrustedOrganizations(TrustedOrganization[] calldata _list) external override onlyAdmin { _addTrustedOrganizations(_list); } /** * @inheritdoc IRoninTrustedOrganization */ - function updateTrustedOrganizations(WeightedAddress[] calldata _list) external override onlyAdmin { - WeightedAddress memory _item; - for (uint _i = 0; _i < _list.length; _i++) { - _item = _list[_i]; - - if (_orgs.contains(_item.addr) && _item.weight > 0) { - _totalWeights -= _weight[_item.addr]; - _totalWeights += _item.weight; - _weight[_item.addr] = _item.weight; - emit TrustedOrganizationUpdated(_item); - } + function updateTrustedOrganizations(TrustedOrganization[] calldata _list) external override onlyAdmin { + require(_list.length > 0, "RoninTrustedOrganization: invalid array length"); + for (uint256 _i; _i < _list.length; _i++) { + _updateTrustedOrganization(_list[_i]); } + emit TrustedOrganizationsUpdated(_list); } /** * @inheritdoc IRoninTrustedOrganization */ function removeTrustedOrganizations(address[] calldata _list) external override onlyAdmin { + require(_list.length > 0, "RoninTrustedOrganization: invalid array length"); for (uint _i = 0; _i < _list.length; _i++) { - if (_orgs.remove(_list[_i])) { - _totalWeights -= _weight[_list[_i]]; - delete _weight[_list[_i]]; - emit TrustedOrganizationRemoved(_list[_i]); - } + _removeTrustedOrganization(_list[_i]); } + emit TrustedOrganizationsRemoved(_list); } /** * @inheritdoc IRoninTrustedOrganization */ function totalWeights() external view virtual returns (uint256) { - return _totalWeights; + return _totalWeight; + } + + /** + * @inheritdoc IRoninTrustedOrganization + */ + function getConsensusWeight(address _consensusAddr) external view returns (uint256) { + return _consensusWeight[_consensusAddr]; } /** * @inheritdoc IRoninTrustedOrganization */ - function getWeight(address _addr) external view override returns (uint256) { - return _weight[_addr]; + function getGovernorWeight(address _governor) external view returns (uint256) { + return _governorWeight[_governor]; + } + + /** + * @inheritdoc IRoninTrustedOrganization + */ + function getBridgeVoterWeight(address _addr) external view returns (uint256) { + return _bridgeVoterWeight[_addr]; + } + + /** + * @inheritdoc IRoninTrustedOrganization + */ + function getConsensusWeights(address[] calldata _list) external view returns (uint256[] memory _res) { + _res = new uint256[](_list.length); + for (uint _i = 0; _i < _res.length; _i++) { + _res[_i] = _consensusWeight[_list[_i]]; + } } /** * @inheritdoc IRoninTrustedOrganization */ - function getWeights(address[] calldata _list) external view override returns (uint256[] memory _res) { + function getGovernorWeights(address[] calldata _list) external view returns (uint256[] memory _res) { _res = new uint256[](_list.length); for (uint _i = 0; _i < _res.length; _i++) { - _res[_i] = _weight[_list[_i]]; + _res[_i] = _governorWeight[_list[_i]]; } } /** * @inheritdoc IRoninTrustedOrganization */ - function sumWeights(address[] calldata _list) external view override returns (uint256 _res) { + function getBridgeVoterWeights(address[] calldata _list) external view returns (uint256[] memory _res) { + _res = new uint256[](_list.length); + for (uint _i = 0; _i < _res.length; _i++) { + _res[_i] = _bridgeVoterWeight[_list[_i]]; + } + } + + /** + * @inheritdoc IRoninTrustedOrganization + */ + function sumConsensusWeights(address[] calldata _list) external view returns (uint256 _res) { for (uint _i = 0; _i < _list.length; _i++) { - _res += _weight[_list[_i]]; + _res += _consensusWeight[_list[_i]]; } } /** * @inheritdoc IRoninTrustedOrganization */ - function getTrustedOrganizationAt(uint256 _idx) external view override returns (WeightedAddress memory _res) { - _res.addr = _orgs.at(_idx); - _res.weight = _weight[_res.addr]; + function sumGovernorWeights(address[] calldata _list) external view returns (uint256 _res) { + for (uint _i = 0; _i < _list.length; _i++) { + _res += _governorWeight[_list[_i]]; + } + } + + /** + * @inheritdoc IRoninTrustedOrganization + */ + function sumBridgeVoterWeights(address[] calldata _list) external view returns (uint256 _res) { + for (uint _i = 0; _i < _list.length; _i++) { + _res += _bridgeVoterWeight[_list[_i]]; + } } /** * @inheritdoc IRoninTrustedOrganization */ function countTrustedOrganizations() external view override returns (uint256) { - return _orgs.length(); + return _consensusList.length; } /** * @inheritdoc IRoninTrustedOrganization */ - function getAllTrustedOrganizations() external view override returns (WeightedAddress[] memory _res) { - address[] memory _list = _orgs.values(); - _res = new WeightedAddress[](_list.length); - for (uint _i = 0; _i < _res.length; _i++) { - _res[_i].addr = _list[_i]; - _res[_i].weight = _weight[_list[_i]]; + function getAllTrustedOrganizations() external view override returns (TrustedOrganization[] memory _list) { + _list = new TrustedOrganization[](_consensusList.length); + address _addr; + for (uint256 _i; _i < _list.length; _i++) { + _addr = _consensusList[_i]; + _list[_i].consensusAddr = _addr; + _list[_i].governor = _governorList[_i]; + _list[_i].bridgeVoter = _bridgeVoterList[_i]; + _list[_i].weight = _consensusWeight[_addr]; } } /** - * @dev Adds a list of addresses into the trusted organization. + * @inheritdoc IRoninTrustedOrganization */ - function _addTrustedOrganizations(WeightedAddress[] calldata _list) internal { - for (uint _i = 0; _i < _list.length; _i++) { - if (_list[_i].weight > 0) { - if (_orgs.add(_list[_i].addr)) { - _totalWeights += _list[_i].weight; - _weight[_list[_i].addr] = _list[_i].weight; - emit TrustedOrganizationAdded(_list[_i]); + function getTrustedOrganization(address _consensusAddr) external view returns (TrustedOrganization memory) { + for (uint _i = 0; _i < _consensusList.length; _i++) { + if (_consensusList[_i] == _consensusAddr) { + return getTrustedOrganizationAt(_i); + } + } + revert("RoninTrustedOrganization: query for non-existent consensus address"); + } + + /** + * @inheritdoc IRoninTrustedOrganization + */ + function getTrustedOrganizationAt(uint256 _idx) public view override returns (TrustedOrganization memory) { + address _addr = _consensusList[_idx]; + return + TrustedOrganization( + _addr, + _governorList[_idx], + _bridgeVoterList[_idx], + _consensusWeight[_addr], + _addedBlock[_addr] + ); + } + + /** + * @dev Adds a list of trusted organizations. + */ + function _addTrustedOrganizations(TrustedOrganization[] calldata _list) internal virtual { + for (uint256 _i; _i < _list.length; _i++) { + _addTrustedOrganization(_list[_i]); + } + emit TrustedOrganizationsAdded(_list); + } + + /** + * @dev Adds a trusted organization. + * + * Requirements: + * - The weight is larger than 0. + * - The consensus address is not added. + * - The govenor address is not added. + * - The bridge voter address is not added. + * + */ + function _addTrustedOrganization(TrustedOrganization memory _v) internal virtual { + require(_v.addedBlock == 0, "RoninTrustedOrganization: invalid request"); + require(_v.weight > 0, "RoninTrustedOrganization: invalid weight"); + + if (_consensusWeight[_v.consensusAddr] > 0) { + revert( + string( + abi.encodePacked( + "RoninTrustedOrganization: consensus address ", + Strings.toHexString(uint160(_v.consensusAddr), 20), + " is added already" + ) + ) + ); + } + + if (_governorWeight[_v.governor] > 0) { + revert( + string( + abi.encodePacked( + "RoninTrustedOrganization: govenor address ", + Strings.toHexString(uint160(_v.governor), 20), + " is added already" + ) + ) + ); + } + + if (_bridgeVoterWeight[_v.bridgeVoter] > 0) { + revert( + string( + abi.encodePacked( + "RoninTrustedOrganization: bridge voter address ", + Strings.toHexString(uint160(_v.bridgeVoter), 20), + " is added already" + ) + ) + ); + } + + _consensusList.push(_v.consensusAddr); + _consensusWeight[_v.consensusAddr] = _v.weight; + + _governorList.push(_v.governor); + _governorWeight[_v.governor] = _v.weight; + + _bridgeVoterList.push(_v.bridgeVoter); + _bridgeVoterWeight[_v.bridgeVoter] = _v.weight; + + _addedBlock[_v.consensusAddr] = block.number; + + _totalWeight += _v.weight; + } + + /** + * @dev Updates a trusted organization. + * + * Requirements: + * - The weight is larger than 0. + * - The consensus address is already added. + * + */ + function _updateTrustedOrganization(TrustedOrganization memory _v) internal virtual { + require(_v.weight > 0, "RoninTrustedOrganization: invalid weight"); + + uint256 _weight = _consensusWeight[_v.consensusAddr]; + if (_weight == 0) { + revert( + string( + abi.encodePacked( + "RoninTrustedOrganization: consensus address ", + Strings.toHexString(uint160(_v.consensusAddr), 20), + " is not added" + ) + ) + ); + } + + uint256 _count = _consensusList.length; + for (uint256 _i = 0; _i < _count; _i++) { + if (_consensusList[_i] == _v.consensusAddr) { + _totalWeight -= _weight; + _totalWeight += _v.weight; + + if (_governorList[_i] != _v.governor) { + require(_governorWeight[_v.governor] == 0, "RoninTrustedOrganization: query for duplicated governor"); + delete _governorWeight[_governorList[_i]]; + _governorList[_i] = _v.governor; + } + + if (_bridgeVoterList[_i] != _v.bridgeVoter) { + require( + _bridgeVoterWeight[_v.bridgeVoter] == 0, + "RoninTrustedOrganization: query for duplicated bridge voter" + ); + delete _bridgeVoterWeight[_bridgeVoterList[_i]]; + _bridgeVoterList[_i] = _v.governor; } + + _consensusWeight[_v.consensusAddr] = _v.weight; + _governorWeight[_v.governor] = _v.weight; + _bridgeVoterWeight[_v.bridgeVoter] = _v.weight; + return; } } } + /** + * @dev Removes a trusted organization. + * + * Requirements: + * - The consensus address is added. + * + */ + function _removeTrustedOrganization(address _addr) internal virtual { + uint256 _weight = _consensusWeight[_addr]; + if (_weight == 0) { + revert( + string( + abi.encodePacked( + "RoninTrustedOrganization: consensus address ", + Strings.toHexString(uint160(_addr), 20), + " is not added" + ) + ) + ); + } + + uint256 _index; + uint256 _count = _consensusList.length; + for (uint256 _i = 0; _i < _count; _i++) { + if (_consensusList[_i] == _addr) { + _index = _i; + break; + } + } + + _totalWeight -= _weight; + + delete _addedBlock[_addr]; + delete _consensusWeight[_addr]; + _consensusList[_index] = _consensusList[_count - 1]; + _consensusList.pop(); + + delete _governorWeight[_governorList[_index]]; + _governorList[_index] = _governorList[_count - 1]; + _governorList.pop(); + + delete _bridgeVoterWeight[_bridgeVoterList[_index]]; + _bridgeVoterList[_index] = _bridgeVoterList[_count - 1]; + _bridgeVoterList.pop(); + } + /** * @dev Sets threshold and returns the old one. * diff --git a/contracts/ronin/RoninGatewayV2.sol b/contracts/ronin/RoninGatewayV2.sol new file mode 100644 index 000000000..3b007242a --- /dev/null +++ b/contracts/ronin/RoninGatewayV2.sol @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "../extensions/GatewayV2.sol"; +import "../extensions/isolated-governance/IsolatedGovernance.sol"; +import "../extensions/MinimumWithdrawal.sol"; +import "../interfaces/IERC20Mintable.sol"; +import "../interfaces/IERC721Mintable.sol"; +import "../interfaces/IRoninGatewayV2.sol"; +import "../interfaces/IRoninValidatorSet.sol"; +import "../interfaces/collections/IHasValidatorContract.sol"; + +contract RoninGatewayV2 is + GatewayV2, + IsolatedGovernance, + Initializable, + MinimumWithdrawal, + AccessControlEnumerable, + IRoninGatewayV2, + IHasValidatorContract +{ + using Token for Token.Info; + using Transfer for Transfer.Request; + using Transfer for Transfer.Receipt; + + /// @dev Withdrawal unlocker role hash + bytes32 public constant WITHDRAWAL_MIGRATOR = keccak256("WITHDRAWAL_MIGRATOR"); + + /// @dev Flag indicating whether the withdrawal migrate progress is done + bool public withdrawalMigrated; + /// @dev Total withdrawal + uint256 public withdrawalCount; + /// @dev Mapping from chain id => deposit id => deposit vote + mapping(uint256 => mapping(uint256 => IsolatedVote)) public depositVote; + /// @dev Mapping from withdrawal id => mainchain withdrew vote + mapping(uint256 => IsolatedVote) public mainchainWithdrewVote; + /// @dev Mapping from withdrawal id => withdrawal receipt + mapping(uint256 => Transfer.Receipt) public withdrawal; + /// @dev Mapping from withdrawal id => validator address => signatures + mapping(uint256 => mapping(address => bytes)) internal _withdrawalSig; + /// @dev Mapping from token address => chain id => mainchain token address + mapping(address => mapping(uint256 => MappedToken)) internal _mainchainToken; + + IRoninValidatorSet internal _validatorContract; + + fallback() external payable { + _fallback(); + } + + receive() external payable { + _fallback(); + } + + /** + * @dev Initializes contract storage. + */ + function initialize( + address _roleSetter, + uint256 _numerator, + uint256 _denominator, + address[] calldata _withdrawalMigrators, + // _packedAddresses[0]: roninTokens + // _packedAddresses[1]: mainchainTokens + address[][2] calldata _packedAddresses, + // _packedNumbers[0]: chainIds + // _packedNumbers[1]: minimumThresholds + uint256[][2] calldata _packedNumbers, + Token.Standard[] calldata _standards + ) external virtual initializer { + _setupRole(DEFAULT_ADMIN_ROLE, _roleSetter); + _setThreshold(_numerator, _denominator); + if (_packedAddresses[0].length > 0) { + _mapTokens(_packedAddresses[0], _packedAddresses[1], _packedNumbers[0], _standards); + _setMinimumThresholds(_packedAddresses[0], _packedNumbers[1]); + } + + for (uint256 _i; _i < _withdrawalMigrators.length; _i++) { + _grantRole(WITHDRAWAL_MIGRATOR, _withdrawalMigrators[_i]); + } + } + + /** + * @inheritdoc IHasValidatorContract + */ + function validatorContract() external view returns (address) { + return address(_validatorContract); + } + + /** + * @inheritdoc IHasValidatorContract + */ + function setValidatorContract(address _addr) external override onlyAdmin { + _setValidatorContract(_addr); + } + + /** + * @dev Sets the validator contract. + * + * Requirements: + * - The new address is a contract. + * + * Emits the event `ValidatorContractUpdated`. + * + */ + function _setValidatorContract(address _addr) internal { + _validatorContract = IRoninValidatorSet(_addr); + emit ValidatorContractUpdated(_addr); + } + + /** + * @dev Migrates withdrawals. + * + * Requirements: + * - The method caller is the migrator. + * - The arrays have the same length and its length larger than 0. + * + */ + function migrateWithdrawals(Transfer.Request[] calldata _requests, address[] calldata _requesters) + external + onlyRole(WITHDRAWAL_MIGRATOR) + { + require(!withdrawalMigrated, "RoninGatewayV2: withdrawals migrated"); + require(_requesters.length == _requests.length && _requests.length > 0, "RoninGatewayV2: invalid array lengths"); + for (uint256 _i; _i < _requests.length; _i++) { + MappedToken memory _token = getMainchainToken(_requests[_i].tokenAddr, 1); + require(_requests[_i].info.erc == _token.erc, "RoninGatewayV2: invalid token standard"); + _storeAsReceipt(_requests[_i], 1, _requesters[_i], _token.tokenAddr); + } + } + + /** + * @dev Mark the migration as done. + */ + function markWithdrawalMigrated() external { + require( + hasRole(DEFAULT_ADMIN_ROLE, msg.sender) || hasRole(WITHDRAWAL_MIGRATOR, msg.sender), + "RoninGatewayV2: unauthorized sender" + ); + require(!withdrawalMigrated, "RoninGatewayV2: withdrawals migrated"); + withdrawalMigrated = true; + } + + /** + * @dev {IRoninGatewayV2-getWithdrawalSignatures}. + */ + function getWithdrawalSignatures(uint256 _withdrawalId, address[] calldata _validators) + external + view + returns (bytes[] memory _signatures) + { + _signatures = new bytes[](_validators.length); + for (uint256 _i = 0; _i < _validators.length; _i++) { + _signatures[_i] = _withdrawalSig[_withdrawalId][_validators[_i]]; + } + } + + /** + * @dev {IRoninGatewayV2-depositFor}. + */ + function depositFor(Transfer.Receipt calldata _receipt) external { + address _sender = msg.sender; + uint256 _weight = _getValidatorWeight(_sender); + _depositFor(_receipt, _sender, _weight, minimumVoteWeight()); + } + + /** + * @dev {IRoninGatewayV2-tryBulkAcknowledgeMainchainWithdrew}. + */ + function tryBulkAcknowledgeMainchainWithdrew(uint256[] calldata _withdrawalIds) + external + returns (bool[] memory _executedReceipts) + { + address _governor = msg.sender; + uint256 _weight = _getValidatorWeight(_governor); + uint256 _minVoteWeight = minimumVoteWeight(); + + uint256 _withdrawalId; + _executedReceipts = new bool[](_withdrawalIds.length); + for (uint256 _i; _i < _withdrawalIds.length; _i++) { + _withdrawalId = _withdrawalIds[_i]; + if (mainchainWithdrew(_withdrawalId)) { + _executedReceipts[_i] = true; + } else { + IsolatedVote storage _proposal = mainchainWithdrewVote[_withdrawalId]; + Transfer.Receipt memory _withdrawal = withdrawal[_withdrawalId]; + bytes32 _hash = _withdrawal.hash(); + VoteStatus _status = _castVote(_proposal, _governor, _weight, _minVoteWeight, _hash); + if (_status == VoteStatus.Approved) { + _proposal.status = VoteStatus.Executed; + emit MainchainWithdrew(_hash, _withdrawal); + } + } + } + } + + /** + * @dev {IRoninGatewayV2-tryBulkDepositFor}. + */ + function tryBulkDepositFor(Transfer.Receipt[] calldata _receipts) external returns (bool[] memory _executedReceipts) { + address _sender = msg.sender; + uint256 _weight = _getValidatorWeight(_sender); + + Transfer.Receipt memory _receipt; + _executedReceipts = new bool[](_receipts.length); + uint256 _minVoteWeight = minimumVoteWeight(); + for (uint256 _i; _i < _receipts.length; _i++) { + _receipt = _receipts[_i]; + if (depositVote[_receipt.mainchain.chainId][_receipt.id].status == VoteStatus.Executed) { + _executedReceipts[_i] = true; + } else { + _depositFor(_receipt, _sender, _weight, _minVoteWeight); + } + } + } + + /** + * @dev {IRoninGatewayV2-requestWithdrawalFor}. + */ + function requestWithdrawalFor(Transfer.Request calldata _request, uint256 _chainId) external whenNotPaused { + _requestWithdrawalFor(_request, msg.sender, _chainId); + } + + /** + * @dev {IRoninGatewayV2-bulkRequestWithdrawalFor}. + */ + function bulkRequestWithdrawalFor(Transfer.Request[] calldata _requests, uint256 _chainId) external whenNotPaused { + require(_requests.length > 0, "RoninGatewayV2: empty array"); + for (uint256 _i; _i < _requests.length; _i++) { + _requestWithdrawalFor(_requests[_i], msg.sender, _chainId); + } + } + + /** + * @dev {IRoninGatewayV2-requestWithdrawalSignatures}. + */ + function requestWithdrawalSignatures(uint256 _withdrawalId) external whenNotPaused { + require(!mainchainWithdrew(_withdrawalId), "RoninGatewayV2: withdrew on mainchain already"); + Transfer.Receipt memory _receipt = withdrawal[_withdrawalId]; + require(_receipt.ronin.chainId == block.chainid, "RoninGatewayV2: query for invalid withdrawal"); + emit WithdrawalSignaturesRequested(_receipt.hash(), _receipt); + } + + /** + * @dev {IRoninGatewayV2-bulkSubmitWithdrawalSignatures}. + */ + function bulkSubmitWithdrawalSignatures(uint256[] calldata _withdrawals, bytes[] calldata _signatures) external { + address _validator = msg.sender; + // This checks method caller already + _getValidatorWeight(_validator); + + require( + _withdrawals.length > 0 && _withdrawals.length == _signatures.length, + "RoninGatewayV2: invalid array length" + ); + for (uint256 _i; _i < _withdrawals.length; _i++) { + _withdrawalSig[_withdrawals[_i]][_validator] = _signatures[_i]; + } + } + + /** + * @dev {IRoninGatewayV2-mapTokens}. + */ + function mapTokens( + address[] calldata _roninTokens, + address[] calldata _mainchainTokens, + uint256[] calldata _chainIds, + Token.Standard[] calldata _standards + ) external onlyAdmin { + require(_roninTokens.length > 0, "RoninGatewayV2: invalid array length"); + _mapTokens(_roninTokens, _mainchainTokens, _chainIds, _standards); + } + + /** + * @dev {IRoninGatewayV2-depositVoted}. + */ + function depositVoted( + uint256 _chainId, + uint256 _depositId, + address _voter + ) external view returns (bool) { + return _voted(depositVote[_chainId][_depositId], _voter); + } + + /** + * @dev {IRoninGatewayV2-mainchainWithdrewVoted}. + */ + function mainchainWithdrewVoted(uint256 _withdrawalId, address _voter) external view returns (bool) { + return _voted(mainchainWithdrewVote[_withdrawalId], _voter); + } + + /** + * @dev {IRoninGatewayV2-mainchainWithdrew}. + */ + function mainchainWithdrew(uint256 _withdrawalId) public view returns (bool) { + return mainchainWithdrewVote[_withdrawalId].status == VoteStatus.Executed; + } + + /** + * @dev {IRoninGatewayV2-getMainchainToken}. + */ + function getMainchainToken(address _roninToken, uint256 _chainId) public view returns (MappedToken memory _token) { + _token = _mainchainToken[_roninToken][_chainId]; + require(_token.tokenAddr != address(0), "RoninGatewayV2: unsupported token"); + } + + /** + * @dev Maps Ronin tokens to mainchain networks. + * + * Requirement: + * - The arrays have the same length. + * + * Emits the `TokenMapped` event. + * + */ + function _mapTokens( + address[] calldata _roninTokens, + address[] calldata _mainchainTokens, + uint256[] calldata _chainIds, + Token.Standard[] calldata _standards + ) internal { + require( + _roninTokens.length == _mainchainTokens.length && _roninTokens.length == _chainIds.length, + "RoninGatewayV2: invalid array length" + ); + + for (uint256 _i; _i < _roninTokens.length; _i++) { + _mainchainToken[_roninTokens[_i]][_chainIds[_i]].tokenAddr = _mainchainTokens[_i]; + _mainchainToken[_roninTokens[_i]][_chainIds[_i]].erc = _standards[_i]; + } + + emit TokenMapped(_roninTokens, _mainchainTokens, _chainIds, _standards); + } + + /** + * @dev Deposits based on the receipt. + * + * Emits the `Deposited` once the assets are released. + * + */ + function _depositFor( + Transfer.Receipt memory _receipt, + address _validator, + uint256 _weight, + uint256 _minVoteWeight + ) internal { + uint256 _id = _receipt.id; + _receipt.info.validate(); + require(_receipt.kind == Transfer.Kind.Deposit, "RoninGatewayV2: invalid receipt kind"); + require(_receipt.ronin.chainId == block.chainid, "RoninGatewayV2: invalid chain id"); + MappedToken memory _token = getMainchainToken(_receipt.ronin.tokenAddr, _receipt.mainchain.chainId); + require( + _token.erc == _receipt.info.erc && _token.tokenAddr == _receipt.mainchain.tokenAddr, + "RoninGatewayV2: invalid receipt" + ); + + IsolatedVote storage _proposal = depositVote[_receipt.mainchain.chainId][_id]; + bytes32 _receiptHash = _receipt.hash(); + VoteStatus _status = _castVote(_proposal, _validator, _weight, _minVoteWeight, _receiptHash); + if (_status == VoteStatus.Approved) { + _proposal.status = VoteStatus.Executed; + _receipt.info.handleAssetTransfer(payable(_receipt.ronin.addr), _receipt.ronin.tokenAddr, IWETH(address(0))); + emit Deposited(_receiptHash, _receipt); + } + } + + /** + * @dev Returns the validator weight. + * + * Requirements: + * - The `_addr` weight is larger than 0. + * + */ + function _getValidatorWeight(address _addr) internal view returns (uint256 _weight) { + _weight = _validatorContract.isBridgeOperator(_addr) ? 1 : 0; + require(_weight > 0, "RoninGatewayV2: unauthorized sender"); + } + + /** + * @dev Locks the assets and request withdrawal. + * + * Requirements: + * - The token info is valid. + * + * Emits the `WithdrawalRequested` event. + * + */ + function _requestWithdrawalFor( + Transfer.Request calldata _request, + address _requester, + uint256 _chainId + ) internal { + _request.info.validate(); + _checkWithdrawal(_request); + MappedToken memory _token = getMainchainToken(_request.tokenAddr, _chainId); + require(_request.info.erc == _token.erc, "RoninGatewayV2: invalid token standard"); + _request.info.transferFrom(_requester, address(this), _request.tokenAddr); + _storeAsReceipt(_request, _chainId, _requester, _token.tokenAddr); + } + + /** + * @dev Stores the withdrawal request as a receipt. + * + * Emits the `WithdrawalRequested` event. + * + */ + function _storeAsReceipt( + Transfer.Request calldata _request, + uint256 _chainId, + address _requester, + address _mainchainTokenAddr + ) internal { + uint256 _withdrawalId = withdrawalCount++; + Transfer.Receipt memory _receipt = _request.into_withdrawal_receipt( + _requester, + _withdrawalId, + _mainchainTokenAddr, + _chainId + ); + withdrawal[_withdrawalId] = _receipt; + emit WithdrawalRequested(_receipt.hash(), _receipt); + } + + /** + * @dev Don't send me RON. + */ + function _fallback() internal virtual { + revert("RoninGatewayV2: invalid request"); + } + + /** + * @inheritdoc GatewayV2 + */ + function _getTotalWeight() internal view virtual override returns (uint256) { + return _validatorContract.totalBridgeOperators(); + } +} diff --git a/contracts/ronin/RoninGovernanceAdmin.sol b/contracts/ronin/RoninGovernanceAdmin.sol index 23d422dd6..a47dca42b 100644 --- a/contracts/ronin/RoninGovernanceAdmin.sol +++ b/contracts/ronin/RoninGovernanceAdmin.sol @@ -7,6 +7,9 @@ import "../extensions/GovernanceAdmin.sol"; import "../interfaces/IBridge.sol"; contract RoninGovernanceAdmin is GovernanceAdmin, GovernanceProposal, BOsGovernanceProposal { + /// @dev Emitted when the bridge operators are approved. + event BridgeOperatorsApproved(uint256 _period, address[] _operators); + modifier onlyGovernor() { require(_getWeight(msg.sender) > 0, "GovernanceAdmin: sender is not governor"); _; @@ -175,36 +178,45 @@ contract RoninGovernanceAdmin is GovernanceAdmin, GovernanceProposal, BOsGoverna */ function voteBridgeOperatorsBySignatures( uint256 _period, - WeightedAddress[] calldata _operators, + address[] calldata _operators, Signature[] calldata _signatures ) external { _castVotesBySignatures(_operators, _signatures, _period, _getMinimumVoteWeight(), DOMAIN_SEPARATOR); IsolatedVote storage _v = _vote[_period]; if (_v.status == VoteStatus.Approved) { _lastSyncedPeriod = _period; + emit BridgeOperatorsApproved(_period, _operators); _v.status = VoteStatus.Executed; - _bridgeContract.replaceBridgeOperators(_operators); } } /** - * @dev Override {CoreGovernance-_getWeight}. + * @inheritdoc GovernanceProposal */ - function _getWeight(address _governor) - internal - view - virtual - override(BOsGovernanceProposal, GovernanceProposal) - returns (uint256) - { + function _getWeight(address _governor) internal view virtual override returns (uint256) { + (bool _success, bytes memory _returndata) = roninTrustedOrganizationContract().staticcall( + abi.encodeWithSelector( + // TransparentUpgradeableProxyV2.functionDelegateCall.selector, + 0x4bb5274a, + abi.encodeWithSelector(IRoninTrustedOrganization.getGovernorWeight.selector, _governor) + ) + ); + require(_success, "GovernanceAdmin: proxy call `getGovernorWeight(address)` failed"); + return abi.decode(_returndata, (uint256)); + } + + /** + * @inheritdoc BOsGovernanceProposal + */ + function _getBridgeVoterWeight(address _governor) internal view virtual override returns (uint256) { (bool _success, bytes memory _returndata) = roninTrustedOrganizationContract().staticcall( abi.encodeWithSelector( // TransparentUpgradeableProxyV2.functionDelegateCall.selector, 0x4bb5274a, - abi.encodeWithSelector(IRoninTrustedOrganization.getWeight.selector, _governor) + abi.encodeWithSelector(IRoninTrustedOrganization.getBridgeVoterWeight.selector, _governor) ) ); - require(_success, "GovernanceAdmin: proxy call `getWeight(address)` failed"); + require(_success, "GovernanceAdmin: proxy call `getBridgeVoterWeight(address)` failed"); return abi.decode(_returndata, (uint256)); } } diff --git a/contracts/ronin/SlashIndicator.sol b/contracts/ronin/SlashIndicator.sol index 6a220f5e8..5b5367a36 100644 --- a/contracts/ronin/SlashIndicator.sol +++ b/contracts/ronin/SlashIndicator.sol @@ -6,6 +6,8 @@ import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "../interfaces/ISlashIndicator.sol"; import "../extensions/collections/HasValidatorContract.sol"; import "../extensions/collections/HasMaintenanceContract.sol"; +import "../extensions/collections/HasRoninTrustedOrganizationContract.sol"; +import "../extensions/collections/HasRoninGovernanceAdminContract.sol"; import "../libraries/Math.sol"; import "../precompile-usages/PrecompileUsageValidateDoubleSign.sol"; @@ -14,6 +16,8 @@ contract SlashIndicator is PrecompileUsageValidateDoubleSign, HasValidatorContract, HasMaintenanceContract, + HasRoninTrustedOrganizationContract, + HasRoninGovernanceAdminContract, Initializable { using Math for uint256; @@ -33,11 +37,15 @@ contract SlashIndicator is uint256 public misdemeanorThreshold; /// @dev The threshold to slash when validator is unavailability reaches felony uint256 public felonyThreshold; + /// @dev The threshold to slash when a trusted organization does not vote for bridge operators + uint256 public bridgeVotingThreshold; /// @dev The amount of RON to slash felony. uint256 public slashFelonyAmount; /// @dev The amount of RON to slash double sign. uint256 public slashDoubleSignAmount; + /// @dev The amount of RON to slash bridge voting. + uint256 public bridgeVotingSlashAmount; /// @dev The block duration to jail a validator that reaches felony thresold. uint256 public felonyJailDuration; /// @dev The block number that the punished validator will be jailed until, due to double signing. @@ -67,18 +75,26 @@ contract SlashIndicator is function initialize( address __validatorContract, address __maintenanceContract, + address __roninTrustedOrganizationContract, + address __roninGovernanceAdminContract, uint256 _misdemeanorThreshold, uint256 _felonyThreshold, + uint256 _bridgeVotingThreshold, uint256 _slashFelonyAmount, uint256 _slashDoubleSignAmount, + uint256 _bridgeVotingSlashAmount, uint256 _felonyJailBlocks, uint256 _doubleSigningConstrainBlocks ) external initializer { _setValidatorContract(__validatorContract); _setMaintenanceContract(__maintenanceContract); + _setRoninTrustedOrganizationContract(__roninTrustedOrganizationContract); + _setRoninGovernanceAdminContract(__roninGovernanceAdminContract); _setSlashThresholds(_felonyThreshold, _misdemeanorThreshold); + _setBridgeVotingThreshold(_bridgeVotingThreshold); _setSlashFelonyAmount(_slashFelonyAmount); _setSlashDoubleSignAmount(_slashDoubleSignAmount); + _setBridgeVotingSlashAmount(_bridgeVotingSlashAmount); _setFelonyJailDuration(_felonyJailBlocks); _setDoubleSigningConstrainBlocks(_doubleSigningConstrainBlocks); _setDoubleSigningJailUntilBlock(type(uint256).max); @@ -140,6 +156,24 @@ contract SlashIndicator is } } + /** + * @inheritdoc ISlashIndicator + */ + function slashBridgeVoting(address _consensusAddr) external { + IRoninTrustedOrganization.TrustedOrganization memory _org = _roninTrustedOrganizationContract + .getTrustedOrganization(_consensusAddr); + uint256 _lastVotedBlock = Math.max(_roninGovernanceAdminContract.lastVotedBlock(_org.bridgeVoter), _org.addedBlock); + uint256 _period = _validatorContract.periodOf(block.number); + if ( + block.number - _lastVotedBlock > bridgeVotingThreshold && + _unavailabilitySlashed[_consensusAddr][_period] != SlashType.BRIDGE_VOTING + ) { + _unavailabilitySlashed[_consensusAddr][_period] = SlashType.BRIDGE_VOTING; + emit UnavailabilitySlashed(_consensusAddr, SlashType.BRIDGE_VOTING, _period); + _validatorContract.slash(_consensusAddr, 0, bridgeVotingSlashAmount); + } + } + /////////////////////////////////////////////////////////////////////////////////////// // GOVERNANCE FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// @@ -172,6 +206,20 @@ contract SlashIndicator is _setFelonyJailDuration(_felonyJailDuration); } + /** + * @inheritdoc ISlashIndicator + */ + function setBridgeVotingThreshold(uint256 _threshold) external override onlyAdmin { + _setBridgeVotingThreshold(_threshold); + } + + /** + * @inheritdoc ISlashIndicator + */ + function setBridgeVotingSlashAmount(uint256 _amount) external override onlyAdmin { + _setBridgeVotingSlashAmount(_amount); + } + /////////////////////////////////////////////////////////////////////////////////////// // QUERY FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// @@ -285,6 +333,22 @@ contract SlashIndicator is emit DoubleSigningJailUntilBlockUpdated(_doubleSigningJailUntilBlock); } + /** + * @dev Sets the threshold to slash when trusted organization does not vote for bridge operators. + */ + function _setBridgeVotingThreshold(uint256 _threshold) internal { + bridgeVotingThreshold = _threshold; + emit BridgeVotingThresholdUpdated(_threshold); + } + + /** + * @dev Sets the amount of RON to slash bridge voting. + */ + function _setBridgeVotingSlashAmount(uint256 _amount) internal { + bridgeVotingSlashAmount = _amount; + emit BridgeVotingSlashAmountUpdated(_amount); + } + /** * @dev Sanity check the address to be slashed */ diff --git a/contracts/ronin/validator/RoninValidatorSet.sol b/contracts/ronin/validator/RoninValidatorSet.sol index d63687bb1..0d09a6bce 100644 --- a/contracts/ronin/validator/RoninValidatorSet.sol +++ b/contracts/ronin/validator/RoninValidatorSet.sol @@ -518,7 +518,7 @@ contract RoninValidatorSet is // Read more about slashing issue at: https://www.notion.so/skymavis/Slashing-Issue-9610ae1452434faca1213ab2e1d7d944 uint256 _minBalance = _stakingContract.minValidatorBalance(); _balanceWeights = _filterUnsatisfiedCandidates(_minBalance); - uint256[] memory _trustedWeights = _roninTrustedOrganizationContract.getWeights(_candidates); + uint256[] memory _trustedWeights = _roninTrustedOrganizationContract.getConsensusWeights(_candidates); uint256 _newValidatorCount; (_newValidators, _newValidatorCount) = _pcPickValidatorSet( _candidates, diff --git a/src/config.ts b/src/config.ts index d5988d129..39042f063 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,7 +1,7 @@ import { BigNumber, BigNumberish } from 'ethers'; import { ethers } from 'hardhat'; import { Address } from 'hardhat-deploy/dist/types'; -import { WeightedAddressStruct } from './types/IBridge'; +import { TrustedOrganizationStruct } from './types/IRoninTrustedOrganization'; export enum Network { Hardhat = 'hardhat', @@ -50,7 +50,7 @@ export interface MaintenanceArguments { } export interface RoninTrustedOrganizationArguments { - trustedOrganizations?: WeightedAddressStruct[]; + trustedOrganizations?: TrustedOrganizationStruct[]; numerator?: BigNumberish; denominator?: BigNumberish; } @@ -84,8 +84,10 @@ export interface StakingVestingConfig { export interface SlashIndicatorArguments { misdemeanorThreshold?: BigNumberish; felonyThreshold?: BigNumberish; + bridgeVotingThreshold?: BigNumberish; slashFelonyAmount?: BigNumberish; slashDoubleSignAmount?: BigNumberish; + bridgeVotingSlashAmount?: BigNumberish; felonyJailBlocks?: BigNumberish; doubleSigningConstrainBlocks?: BigNumberish; } @@ -167,8 +169,10 @@ export const slashIndicatorConf: SlashIndicatorConfig = { [Network.Devnet]: { misdemeanorThreshold: 50, felonyThreshold: 150, + bridgeVotingThreshold: 28800 * 3, // ~3 days slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), // 1 RON slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), // 10 RON + bridgeVotingSlashAmount: BigNumber.from(10).pow(18).mul(10_000), // 10.000 RON felonyJailBlocks: 28800 * 2, // jails for 2 days doubleSigningConstrainBlocks: 28800, }, @@ -194,8 +198,14 @@ export const roninValidatorSetConf: RoninValidatorSetConfig = { export const roninTrustedOrganizationConf: RoninTrustedOrganizationConfig = { [Network.Hardhat]: undefined, [Network.Devnet]: { - trustedOrganizations: ['0x93b8eed0a1e082ae2f478fd7f8c14b1fc0261bb1'].map((addr) => ({ addr, weight: 100 })), - numerator: 1, + trustedOrganizations: ['0x93b8eed0a1e082ae2f478fd7f8c14b1fc0261bb1'].map((addr) => ({ + consensusAddr: addr, + governor: addr, + bridgeVoter: addr, + weight: 100, + addedBlock: 0, + })), + numerator: 0, denominator: 1, }, [Network.Testnet]: undefined, diff --git a/src/deploy/proxy/slash-indicator-proxy.ts b/src/deploy/proxy/slash-indicator-proxy.ts index 24989e6e7..006febc2f 100644 --- a/src/deploy/proxy/slash-indicator-proxy.ts +++ b/src/deploy/proxy/slash-indicator-proxy.ts @@ -18,10 +18,14 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const data = new SlashIndicator__factory().interface.encodeFunctionData('initialize', [ roninInitAddress[network.name]!.validatorContract?.address, roninInitAddress[network.name]!.maintenanceContract?.address, + roninInitAddress[network.name]!.roninTrustedOrganizationContract?.address, + roninInitAddress[network.name]!.governanceAdmin?.address, slashIndicatorConf[network.name]!.misdemeanorThreshold, slashIndicatorConf[network.name]!.felonyThreshold, + slashIndicatorConf[network.name]!.bridgeVotingThreshold, slashIndicatorConf[network.name]!.slashFelonyAmount, slashIndicatorConf[network.name]!.slashDoubleSignAmount, + slashIndicatorConf[network.name]!.bridgeVotingSlashAmount, slashIndicatorConf[network.name]!.felonyJailBlocks, slashIndicatorConf[network.name]!.doubleSigningConstrainBlocks, ]); diff --git a/src/script/governance-admin-interface.ts b/src/script/governance-admin-interface.ts index 662117661..0df032ee2 100644 --- a/src/script/governance-admin-interface.ts +++ b/src/script/governance-admin-interface.ts @@ -65,13 +65,32 @@ export class GovernanceAdminInterface { to, 0, this.interface.encodeFunctionData('functionDelegateCall', [data]), - 1_000_000 + 2_000_000 ); const signatures = await this.generateSignatures(proposal); const supports = signatures.map(() => VoteType.For); return this.contract.connect(this.signers[0]).proposeProposalStructAndCastVotes(proposal, supports, signatures); } + async functionDelegateCalls(toList: Address[], dataList: BytesLike[]) { + if (toList.length != dataList.length || toList.length == 0) { + throw Error('invalid array length'); + } + + const proposal = { + chainId: network.config.chainId!, + nonce: (await this.contract.round(network.config.chainId!)).add(1), + targets: toList, + values: toList.map(() => 0), + calldatas: dataList.map((v) => this.interface.encodeFunctionData('functionDelegateCall', [v])), + gasAmounts: toList.map(() => 2_000_000), + }; + + const signatures = await this.generateSignatures(proposal); + const supports = signatures.map(() => VoteType.For); + return this.contract.connect(this.signers[0]).proposeProposalStructAndCastVotes(proposal, supports, signatures); + } + async upgrade(from: Address, to: Address) { const proposal = await this.createProposal(from, 0, this.interface.encodeFunctionData('upgradeTo', [to]), 500_000); const signatures = await this.generateSignatures(proposal); diff --git a/src/script/proposal.ts b/src/script/proposal.ts index 23988c771..9aad7ee95 100644 --- a/src/script/proposal.ts +++ b/src/script/proposal.ts @@ -1,7 +1,8 @@ import { BigNumberish } from 'ethers'; import { AbiCoder, keccak256, solidityKeccak256 } from 'ethers/lib/utils'; +import { Address } from 'hardhat-deploy/dist/types'; + import { GlobalProposalDetailStruct, ProposalDetailStruct } from '../types/GovernanceAdmin'; -import { WeightedAddressStruct } from '../types/IBridge'; // keccak256("ProposalDetail(uint256 nonce,uint256 chainId,address[] targets,uint256[] values,bytes[] calldatas,uint256[] gasAmounts)") const proposalTypeHash = '0x65526afa953b4e935ecd640e6905741252eedae157e79c37331ee8103c70019d'; @@ -9,10 +10,8 @@ const proposalTypeHash = '0x65526afa953b4e935ecd640e6905741252eedae157e79c37331e const globalProposalTypeHash = '0xdb316eb400de2ddff92ab4255c0cd3cba634cd5236b93386ed9328b7d822d1c7'; // keccak256("Ballot(bytes32 proposalHash,uint8 support)") const ballotTypeHash = '0xd900570327c4c0df8dd6bdd522b7da7e39145dd049d2fd4602276adcd511e3c2'; -// keccak256("BridgeOperatorsBallot(uint256 period,BridgeOperator[] operators)BridgeOperator(address addr,uint256 weight)"); -const bridgeOperatorsBallotTypeHash = '0x086d287088869477577720f66bf2a8412510e726fd1a893739cf6c2280aadcb5'; -// keccak256("BridgeOperator(address addr,uint256 weight)"); -const bridgeOperatorTypeHash = '0xe71132f1797176c8456299d5325989bbf16523f1e2e3aef4554d23f982955a2c'; +// keccak256("BridgeOperatorsBallot(uint256 period,address[] operators)"); +const bridgeOperatorsBallotTypeHash = '0xeea5e3908ac28cbdbbce8853e49444c558a0a03597e98ef19e6ff86162ed9ae3'; export enum VoteType { For = 0, @@ -30,7 +29,6 @@ export const ballotParamTypes = ['bytes32', 'bytes32', 'uint8']; export const proposalParamTypes = ['bytes32', 'uint256', 'uint256', 'bytes32', 'bytes32', 'bytes32', 'bytes32']; export const globalProposalParamTypes = ['bytes32', 'uint256', 'bytes32', 'bytes32', 'bytes32', 'bytes32']; export const bridgeOperatorsBallotParamTypes = ['bytes32', 'uint256', 'bytes32']; -export const bridgeOperatorParamTypes = ['bytes32', 'address', 'uint256']; export const BallotTypes = { Ballot: [ @@ -61,11 +59,7 @@ export const GlobalProposalTypes = { export const BridgeOperatorsBallotTypes = { BridgeOperatorsBallot: [ { name: 'period', type: 'uint256' }, - { name: 'operators', type: 'BridgeOperator[]' }, - ], - BridgeOperator: [ - { name: 'addr', type: 'address' }, - { name: 'weight', type: 'uint256' }, + { name: 'operators', type: 'address[]' }, ], }; @@ -145,30 +139,24 @@ export const getBallotDigest = (domainSeparator: string, proposalHash: string, s export interface BOsBallot { period: BigNumberish; - operators: WeightedAddressStruct[]; + operators: Address[]; } -export const getBOsBallotHash = (period: BigNumberish, operators: WeightedAddressStruct[]) => +export const getBOsBallotHash = (period: BigNumberish, operators: Address[]) => keccak256( AbiCoder.prototype.encode(bridgeOperatorsBallotParamTypes, [ bridgeOperatorsBallotTypeHash, period, keccak256( AbiCoder.prototype.encode( - operators.map(() => 'bytes32'), - operators.map(({ addr, weight }) => - keccak256(AbiCoder.prototype.encode(bridgeOperatorParamTypes, [bridgeOperatorTypeHash, addr, weight])) - ) + operators.map(() => 'address'), + operators ) ), ]) ); -export const getBOsBallotDigest = ( - domainSeparator: string, - period: BigNumberish, - operators: WeightedAddressStruct[] -): string => +export const getBOsBallotDigest = (domainSeparator: string, period: BigNumberish, operators: Address[]): string => solidityKeccak256( ['bytes1', 'bytes1', 'bytes32', 'bytes32'], ['0x19', '0x01', domainSeparator, getBOsBallotHash(period, operators)] diff --git a/test/governance-admin/GovernanceAdmin.test.ts b/test/governance-admin/GovernanceAdmin.test.ts index 7cdedeb35..f02c414bb 100644 --- a/test/governance-admin/GovernanceAdmin.test.ts +++ b/test/governance-admin/GovernanceAdmin.test.ts @@ -45,7 +45,13 @@ describe('Governance Admin test', () => { const { roninGovernanceAdminAddress, mainchainGovernanceAdminAddress, stakingContractAddress } = await initTest( 'RoninGovernanceAdmin.test' )({ - trustedOrganizations: governors.map((v) => ({ addr: v.address, weight: 100 })), + trustedOrganizations: governors.map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), numerator: 1, denominator: 2, relayers: [relayer.address], @@ -93,7 +99,7 @@ describe('Governance Admin test', () => { it('Should be able to vote bridge operators', async () => { ballot = { period: 10, - operators: governors.map((v) => ({ addr: v.address, weight: 100 })), + operators: governors.map((v) => v.address), }; signatures = await Promise.all( governors.map((g) => @@ -103,11 +109,11 @@ describe('Governance Admin test', () => { ) ); await governanceAdmin.voteBridgeOperatorsBySignatures(ballot.period, ballot.operators, signatures); - expect(await bridgeContract.getBridgeOperators()).eql(governors.map((v) => [v.address, BigNumber.from(100)])); }); it('Should be able relay vote bridge operators', async () => { await mainchainGovernanceAdmin.connect(relayer).relayBridgeOperators(ballot.period, ballot.operators, signatures); + expect(await bridgeContract.getBridgeOperators()).eql(governors.map((v) => v.address)); }); it('Should not able to relay again', async () => { @@ -119,7 +125,7 @@ describe('Governance Admin test', () => { it('Should not be able to use the signatures for another period', async () => { ballot = { period: 100, - operators: governors.map((v) => ({ addr: v.address, weight: 100 })), + operators: governors.map((v) => v.address), }; await expect( governanceAdmin.voteBridgeOperatorsBySignatures(ballot.period, ballot.operators, signatures) @@ -129,7 +135,7 @@ describe('Governance Admin test', () => { it('Should not be able to vote bridge operators with a smaller period', async () => { ballot = { period: 5, - operators: governors.map((v) => ({ addr: v.address, weight: 100 })), + operators: governors.map((v) => v.address), }; signatures = await Promise.all( governors.map((g) => diff --git a/test/helpers/fixture.ts b/test/helpers/fixture.ts index 7e71d3b0f..cbd1ac276 100644 --- a/test/helpers/fixture.ts +++ b/test/helpers/fixture.ts @@ -40,6 +40,8 @@ export const defaultTestConfig = { slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), doubleSigningConstrainBlocks: 28800, + bridgeVotingThreshold: 28800 * 3, + bridgeVotingSlashAmount: BigNumber.from(10).pow(18).mul(10_000), maxValidatorNumber: 4, maxPrioritizedValidatorNumber: 0, @@ -86,6 +88,8 @@ export const initTest = (id: string) => maxSchedules: options?.maxSchedules ?? defaultTestConfig.maxSchedules, }; slashIndicatorConf[network.name] = { + bridgeVotingThreshold: options?.bridgeVotingThreshold ?? defaultTestConfig.bridgeVotingThreshold, + bridgeVotingSlashAmount: options?.bridgeVotingSlashAmount ?? defaultTestConfig.bridgeVotingSlashAmount, misdemeanorThreshold: options?.misdemeanorThreshold ?? defaultTestConfig.misdemeanorThreshold, felonyThreshold: options?.felonyThreshold ?? defaultTestConfig.felonyThreshold, slashFelonyAmount: options?.slashFelonyAmount ?? defaultTestConfig.slashFelonyAmount, diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index f3ed66ea1..e946fb5cc 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -55,7 +55,15 @@ describe('[Integration] Slash validators', () => { slashFelonyAmount, slashDoubleSignAmount, minValidatorBalance, - trustedOrganizations: [{ addr: governor.address, weight: 100 }], + trustedOrganizations: [ + { + consensusAddr: governor.address, + governor: governor.address, + bridgeVoter: governor.address, + weight: 100, + addedBlock: 0, + }, + ], }); slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index bd157fa67..bd526ad04 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -49,7 +49,13 @@ describe('[Integration] Submit Block Reward', () => { validatorBonusPerBlock, slashFelonyAmount, slashDoubleSignAmount, - trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: 100 })), + trustedOrganizations: [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), }); slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index fac5ebbc4..4cd213c2f 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -51,7 +51,13 @@ describe('[Integration] Wrap up epoch', () => { slashDoubleSignAmount, minValidatorBalance, maxValidatorNumber, - trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: 100 })), + trustedOrganizations: [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), }); slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); diff --git a/test/integration/Configuration.test.ts b/test/integration/Configuration.test.ts index 148943117..ced8701f5 100644 --- a/test/integration/Configuration.test.ts +++ b/test/integration/Configuration.test.ts @@ -76,7 +76,13 @@ describe('[Integration] Configuration check', () => { maxMaintenanceBlockPeriod, minOffset, maxSchedules, - trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: 100 })), + trustedOrganizations: [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), }); stakingVestingContract = StakingVesting__factory.connect(stakingVestingContractAddress, deployer); diff --git a/test/maintainance/Maintenance.test.ts b/test/maintainance/Maintenance.test.ts index 414bef497..db5d9bee5 100644 --- a/test/maintainance/Maintenance.test.ts +++ b/test/maintainance/Maintenance.test.ts @@ -57,7 +57,13 @@ describe('Maintenance test', () => { validatorContractAddress, roninGovernanceAdminAddress, } = await initTest('Maintenance')({ - trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: 100 })), + trustedOrganizations: [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), misdemeanorThreshold, felonyThreshold, }); diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index a35ba1c1d..b9eef71f3 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -73,7 +73,13 @@ describe('Slash indicator test', () => { const { slashContractAddress, stakingContractAddress, validatorContractAddress, roninGovernanceAdminAddress } = await initTest('SlashIndicator')({ - trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: 100 })), + trustedOrganizations: [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), misdemeanorThreshold, felonyThreshold, maxValidatorNumber, diff --git a/test/validator/ArrangeValidators.test.ts b/test/validator/ArrangeValidators.test.ts index d497ea48b..7d547b381 100644 --- a/test/validator/ArrangeValidators.test.ts +++ b/test/validator/ArrangeValidators.test.ts @@ -37,16 +37,24 @@ const setPriorityStatus = async (addrs: Address[], statuses: boolean[]): Promise const arr = statuses.map((stt, i) => ({ address: addrs[i], stt })); const addingTrustedOrgs = arr.filter(({ stt }) => stt).map(({ address }) => address); const removingTrustedOrgs = arr.filter(({ stt }) => !stt).map(({ address }) => address); - await governanceAdminInterface.functionDelegateCall( - roninTrustedOrganization.address, - roninTrustedOrganization.interface.encodeFunctionData('addTrustedOrganizations', [ - addingTrustedOrgs.map((v) => ({ addr: v, weight: defaultTrustedWeight })), - ]) - ); - await governanceAdminInterface.functionDelegateCall( - roninTrustedOrganization.address, - roninTrustedOrganization.interface.encodeFunctionData('removeTrustedOrganizations', [removingTrustedOrgs]) - ); + if (addingTrustedOrgs.length > 0) { + await governanceAdminInterface.functionDelegateCalls( + addingTrustedOrgs.map(() => roninTrustedOrganization.address), + addingTrustedOrgs.map((v) => + roninTrustedOrganization.interface.encodeFunctionData('addTrustedOrganizations', [ + [{ consensusAddr: v, governor: v, bridgeVoter: v, weight: 100, addedBlock: 0 }], + ]) + ) + ); + } + if (removingTrustedOrgs.length > 0) { + await governanceAdminInterface.functionDelegateCalls( + removingTrustedOrgs.map(() => roninTrustedOrganization.address), + removingTrustedOrgs.map((v) => + roninTrustedOrganization.interface.encodeFunctionData('removeTrustedOrganizations', [[v]]) + ) + ); + } return statuses.map((stt) => (stt ? defaultTrustedWeight : 0)); }; @@ -91,7 +99,13 @@ describe('Arrange validators', () => { maxValidatorCandidate, maxPrioritizedValidatorNumber, slashFelonyAmount, - trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: defaultTrustedWeight })), + trustedOrganizations: [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), }); validatorContract = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); @@ -409,7 +423,6 @@ describe('Arrange validators', () => { }); it('Shuffled: Actual(prioritized) == MaxNum(prioritized); Actual(regular) == MaxNum(regular)', async () => { - // prettier-ignore let indexes = [0, 1, 2, 3, 4, 5, 6]; let statuses = [true, false, true, true, false, true, false]; @@ -430,7 +443,6 @@ describe('Arrange validators', () => { }); it('Shuffled: Actual(prioritized) > MaxNum(prioritized); Actual(regular) < MaxNum(regular)', async () => { - // prettier-ignore let indexes = [0, 1, 2, 3, 4, 5, 6]; let statuses = [true, false, true, true, false, true, true]; @@ -450,8 +462,7 @@ describe('Arrange validators', () => { }); it('Shuffled: Actual(prioritized) < MaxNum(prioritized); Actual(regular) > MaxNum(regular)', async () => { - // prettier-ignore - let indexes = [0, 1, 2, 3, 4, 5, 6, 7 ]; + let indexes = [0, 1, 2, 3, 4, 5, 6, 7]; let statuses = [true, false, false, false, false, true, true, false]; let inputTrustedWeights = await setPriorityStatusByIndexes(indexes, statuses); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index 1a76df345..a9b7658ee 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -50,7 +50,13 @@ describe('Ronin Validator Set test', () => { const { slashContractAddress, validatorContractAddress, stakingContractAddress, roninGovernanceAdminAddress } = await initTest('RoninValidatorSet')({ - trustedOrganizations: [governor.address].map((addr) => ({ addr, weight: 100 })), + trustedOrganizations: [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), minValidatorBalance, maxValidatorNumber, maxValidatorCandidate, From b1dcb6bd3480474eb6d34cf847af4a886838934e Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 25 Oct 2022 14:04:40 +0700 Subject: [PATCH 177/190] Fix array issue (#37) --- contracts/interfaces/IRoninValidatorSet.sol | 9 +++++++-- contracts/mocks/MockValidatorSet.sol | 4 +++- contracts/ronin/staking/StakingManager.sol | 3 +++ contracts/ronin/validator/RoninValidatorSet.sol | 14 +++++++++++++- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index abe47be26..bd9f0684f 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -98,9 +98,14 @@ interface IRoninValidatorSet is ICandidateManager { function jailed(address[] memory) external view returns (bool[] memory); /** - * @dev Returns whether the incoming reward of the validators are deprecated during the period. + * @dev Returns whether the incoming reward of the validators are deprecated during the current period. */ - function rewardDeprecated(address[] memory, uint256 _period) external view returns (bool[] memory); + function rewardDeprecated(address[] memory) external view returns (bool[] memory); + + /** + * @dev Returns whether the incoming reward of the validators are deprecated during a period. + */ + function rewardDeprecatedAtPeriod(address[] memory, uint256 _period) external view returns (bool[] memory); /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR NORMAL USER // diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index 8d1a273ef..3132c7c47 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -76,7 +76,9 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { function jailed(address[] memory) external view override returns (bool[] memory) {} - function rewardDeprecated(address[] memory, uint256 _period) external view override returns (bool[] memory) {} + function rewardDeprecatedAtPeriod(address[] memory, uint256 _period) external view override returns (bool[] memory) {} + + function rewardDeprecated(address[] memory) external view override returns (bool[] memory) {} function epochOf(uint256 _block) external view override returns (uint256) {} diff --git a/contracts/ronin/staking/StakingManager.sol b/contracts/ronin/staking/StakingManager.sol index 1bbb400e5..55a276bd5 100644 --- a/contracts/ronin/staking/StakingManager.sol +++ b/contracts/ronin/staking/StakingManager.sol @@ -290,6 +290,9 @@ abstract contract StakingManager is returns (uint256[] memory _pendings, uint256[] memory _claimables) { address _consensusAddr; + _pendings = new uint256[](_poolAddrList.length); + _claimables = new uint256[](_poolAddrList.length); + for (uint256 _i = 0; _i < _poolAddrList.length; _i++) { _consensusAddr = _poolAddrList[_i]; diff --git a/contracts/ronin/validator/RoninValidatorSet.sol b/contracts/ronin/validator/RoninValidatorSet.sol index 0d09a6bce..e596f2e00 100644 --- a/contracts/ronin/validator/RoninValidatorSet.sol +++ b/contracts/ronin/validator/RoninValidatorSet.sol @@ -213,6 +213,7 @@ contract RoninValidatorSet is * @inheritdoc IRoninValidatorSet */ function jailed(address[] memory _addrList) external view override returns (bool[] memory _result) { + _result = new bool[](_addrList.length); for (uint256 _i; _i < _addrList.length; _i++) { _result[_i] = _jailed(_addrList[_i]); } @@ -221,12 +222,23 @@ contract RoninValidatorSet is /** * @inheritdoc IRoninValidatorSet */ - function rewardDeprecated(address[] memory _addrList, uint256 _period) + function rewardDeprecated(address[] memory _addrList) external view override returns (bool[] memory _result) { + _result = new bool[](_addrList.length); + for (uint256 _i; _i < _addrList.length; _i++) { + _result[_i] = _rewardDeprecated(_addrList[_i], periodOf(block.number)); + } + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function rewardDeprecatedAtPeriod(address[] memory _addrList, uint256 _period) external view override returns (bool[] memory _result) { + _result = new bool[](_addrList.length); for (uint256 _i; _i < _addrList.length; _i++) { _result[_i] = _rewardDeprecated(_addrList[_i], _period); } From 14d566d176fdcc7d250802a00e99df0dc44451f1 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 25 Oct 2022 16:35:17 +0700 Subject: [PATCH 178/190] Replace method `periodOf` by `currentPeriod`. No longer rescale unavailability threshold (#36) * Replace method periodOf by currentPeriod. No longer rescale unavailability threshold * [Test] Fix general compile error * Fix test/maintainance/Maintenance.test.ts * Fix test/staking/AdvancedCalculation.test.ts * Fix test/staking/Staking.test.ts * Rename function & update comment --- contracts/interfaces/ICandidateManager.sol | 12 +-- contracts/interfaces/IRoninValidatorSet.sol | 22 +---- contracts/interfaces/ISlashIndicator.sol | 13 --- contracts/libraries/Math.sol | 11 --- .../mocks/MockRoninValidatorSetExtended.sol | 22 ----- contracts/mocks/MockStaking.sol | 16 ++-- contracts/mocks/MockValidatorSet.sol | 33 +++----- contracts/ronin/Maintenance.sol | 6 -- contracts/ronin/SlashIndicator.sol | 74 +++-------------- contracts/ronin/staking/RewardCalculation.sol | 10 +-- contracts/ronin/staking/Staking.sol | 6 +- .../ronin/validator/CandidateManager.sol | 14 ++-- .../ronin/validator/RoninValidatorSet.sol | 81 ++++++++----------- src/config.ts | 2 - src/deploy/proxy/ronin-validator-proxy.ts | 1 - test/helpers/fixture.ts | 5 +- test/helpers/ronin-validator-set.ts | 51 +++--------- .../integration/ActionSlashValidators.test.ts | 38 ++++----- test/integration/ActionSubmitReward.test.ts | 4 +- test/integration/ActionWrapUpEpoch.test.ts | 12 +-- test/integration/Configuration.test.ts | 7 -- test/maintainance/Maintenance.test.ts | 44 +--------- test/slash/SlashIndicator.test.ts | 34 ++++---- test/staking/Staking.test.ts | 8 +- test/validator/RoninValidatorSet.test.ts | 12 +-- 25 files changed, 150 insertions(+), 388 deletions(-) diff --git a/contracts/interfaces/ICandidateManager.sol b/contracts/interfaces/ICandidateManager.sol index 2715e7a8d..7769683cd 100644 --- a/contracts/interfaces/ICandidateManager.sol +++ b/contracts/interfaces/ICandidateManager.sol @@ -15,8 +15,8 @@ interface ICandidateManager { // The percentile of reward that validators can be received, the rest goes to the delegators. // Values in range [0; 100_00] stands for 0-100% uint256 commissionRate; - // The block that the candidate to be revoked. - uint256 revokedBlock; + // The period that the candidate to be revoked. + uint256 revokedPeriod; // Extra data bytes extraData; } @@ -31,7 +31,7 @@ interface ICandidateManager { address bridgeOperator ); /// @dev Emitted when the revoked block of a candidate is updated. - event CandidateRevokedBlockUpdated(address indexed consensusAddr, uint256 revokedBlock); + event CandidateRevokedPeriodUpdated(address indexed consensusAddr, uint256 revokedPeriod); /// @dev Emitted when the validator candidate is revoked. event CandidatesRevoked(address[] consensusAddrs); @@ -69,7 +69,7 @@ interface ICandidateManager { ) external; /** - * @dev Requests to revoke a validator candidate. + * @dev Requests to revoke a validator candidate at the next period ending. * * Requirements: * - The method caller is staking contract. @@ -105,9 +105,9 @@ interface ICandidateManager { function isCandidateAdmin(address _candidate, address _admin) external view returns (bool); /** - * @dev Returns the number of epochs in a period. + * @dev Returns the period index from the current block. */ - function numberOfEpochsInPeriod() external view returns (uint256 _numberOfEpochs); + function currentPeriod() external view returns (uint256); /** * @dev Returns the number of blocks in a epoch. diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index bd9f0684f..6496e364c 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -11,8 +11,6 @@ interface IRoninValidatorSet is ICandidateManager { event MaxPrioritizedValidatorNumberUpdated(uint256); /// @dev Emitted when the number of blocks in epoch is updated event NumberOfBlocksInEpochUpdated(uint256); - /// @dev Emitted when the number of epochs in period is updated - event NumberOfEpochsInPeriodUpdated(uint256); /// @dev Emitted when the validator set is updated event ValidatorSetUpdated(address[]); /// @dev Emitted when the bridge operator set is updated, to mirror the in-jail and maintaining status of the validator. @@ -126,11 +124,6 @@ interface IRoninValidatorSet is ICandidateManager { */ function epochOf(uint256 _block) external view returns (uint256); - /** - * @dev Returns the period index from the block number. - */ - function periodOf(uint256 _block) external view returns (uint256); - /** * @dev Returns the current validator list. */ @@ -177,9 +170,9 @@ interface IRoninValidatorSet is ICandidateManager { function epochEndingAt(uint256 _block) external view returns (bool); /** - * @dev Returns whether the period ending is at the block number `_block`. + * @dev Returns whether the period ending at the current block number. */ - function periodEndingAt(uint256 _block) external view returns (bool); + function isPeriodEnding() external view returns (bool); /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR ADMIN // @@ -206,15 +199,4 @@ interface IRoninValidatorSet is ICandidateManager { * */ function setNumberOfBlocksInEpoch(uint256 _numberOfBlocksInEpoch) external; - - /** - * @dev Updates the number of epochs in period - * - * Requirements: - * - The method caller is admin - * - * Emits the event `NumberOfEpochsInPeriodUpdated` - * - */ - function setNumberOfEpochsInPeriod(uint256 _numberOfEpochsInPeriod) external; } diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index 62897f876..c179f8d6d 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -134,14 +134,6 @@ interface ISlashIndicator { */ function currentUnavailabilityIndicator(address _validator) external view returns (uint256); - /** - * @dev Returns the scaled thresholds based on the maintenance duration for unavailability slashing. - */ - function unavailabilityThresholdsOf(address _addr, uint256 _block) - external - view - returns (uint256 _misdemeanorThreshold, uint256 _felonyThreshold); - /** * @dev Retursn the unavailability indicator in the period `_period` of a validator. */ @@ -154,9 +146,4 @@ interface ISlashIndicator { external view returns (uint256 _misdemeanorThreshold, uint256 _felonyThreshold); - - /** - * @dev Checks the slashed tier for unavailability of a validator. - */ - function getUnavailabilitySlashType(address _validatorAddr, uint256 _period) external view returns (SlashType); } diff --git a/contracts/libraries/Math.sol b/contracts/libraries/Math.sol index fd3bd85f8..754320a45 100644 --- a/contracts/libraries/Math.sol +++ b/contracts/libraries/Math.sol @@ -27,15 +27,4 @@ library Math { ) internal pure returns (bool) { return a <= c && c <= b; } - - /** - * @dev Returns the result from scaling c to ratio 1-a/b. - */ - function scale( - uint256 c, - uint256 a, - uint256 b - ) internal pure returns (uint256) { - return (c * a) / b; - } } diff --git a/contracts/mocks/MockRoninValidatorSetExtended.sol b/contracts/mocks/MockRoninValidatorSetExtended.sol index 3886ead96..844666d7c 100644 --- a/contracts/mocks/MockRoninValidatorSetExtended.sol +++ b/contracts/mocks/MockRoninValidatorSetExtended.sol @@ -8,7 +8,6 @@ import "../libraries/EnumFlags.sol"; contract MockRoninValidatorSetExtended is MockRoninValidatorSetOverridePrecompile { uint256[] internal _epochs; - uint256[] internal _periods; constructor() {} @@ -16,18 +15,6 @@ contract MockRoninValidatorSetExtended is MockRoninValidatorSetOverridePrecompil _epochs.push(block.number); } - function endPeriod() external { - _periods.push(block.number); - } - - function periodOf(uint256 _block) public view override returns (uint256 _period) { - for (uint256 _i = _periods.length; _i > 0; _i--) { - if (_block >= _periods[_i - 1]) { - return _i; - } - } - } - function epochOf(uint256 _block) public view override returns (uint256 _epoch) { for (uint256 _i = _epochs.length; _i > 0; _i--) { if (_block >= _epochs[_i - 1]) { @@ -45,15 +32,6 @@ contract MockRoninValidatorSetExtended is MockRoninValidatorSetOverridePrecompil return false; } - function periodEndingAt(uint256 _block) public view override returns (bool) { - for (uint _i = 0; _i < _periods.length; _i++) { - if (_block == _periods[_i]) { - return true; - } - } - return false; - } - function getJailUntils(address[] calldata _addrs) public view returns (uint256[] memory jailUntils_) { jailUntils_ = new uint256[](_addrs.length); for (uint _i = 0; _i < _addrs.length; _i++) { diff --git a/contracts/mocks/MockStaking.sol b/contracts/mocks/MockStaking.sol index 56e786d29..bd74aa05e 100644 --- a/contracts/mocks/MockStaking.sol +++ b/contracts/mocks/MockStaking.sol @@ -9,18 +9,18 @@ contract MockStaking is RewardCalculation { mapping(address => uint256) internal _stakingBalance; /// @dev Mapping from period number => slashed mapping(uint256 => bool) internal _periodSlashed; - uint256[] internal _periods; + uint256 internal _lastUpdatedPeriod; uint256 internal _totalBalance; address public poolAddr; constructor(address _poolAddr) { - _periods.push(0); + _lastUpdatedPeriod++; poolAddr = _poolAddr; } function endPeriod() external { - _periods.push(block.number); + _lastUpdatedPeriod++; } function stake(address _user, uint256 _amount) external { @@ -58,7 +58,7 @@ contract MockStaking is RewardCalculation { } function getPeriod() public view returns (uint256) { - return _periodOf(block.number); + return _currentPeriod(); } function claimReward(address _user) external returns (uint256 _amount) { @@ -84,12 +84,8 @@ contract MockStaking is RewardCalculation { return _periodSlashed[_period]; } - function _periodOf(uint256 _block) internal view override returns (uint256 _period) { - for (uint256 _i; _i < _periods.length; _i++) { - if (_block >= _periods[_i]) { - _period = _i + 1; - } - } + function _currentPeriod() internal view override returns (uint256 _period) { + return _lastUpdatedPeriod; } function totalBalances(address[] calldata _poolAddr) external view override returns (uint256[] memory) {} diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index 3132c7c47..d2cecb918 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -11,25 +11,22 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { address public stakingVestingContract; address public slashIndicatorContract; - uint256 internal _numberOfEpochsInPeriod; + uint256 internal _lastUpdatedPeriod; uint256 internal _numberOfBlocksInEpoch; /// @dev Mapping from period number => slashed mapping(uint256 => bool) internal _periodSlashed; - uint256[] internal _periods; constructor( address __stakingContract, address _slashIndicatorContract, address _stakingVestingContract, uint256 __maxValidatorCandidate, - uint256 __numberOfEpochsInPeriod, uint256 __numberOfBlocksInEpoch ) { _setStakingContract(__stakingContract); _setMaxValidatorCandidate(__maxValidatorCandidate); slashIndicatorContract = _slashIndicatorContract; stakingVestingContract = _stakingVestingContract; - _numberOfEpochsInPeriod = __numberOfEpochsInPeriod; _numberOfBlocksInEpoch = __numberOfBlocksInEpoch; } @@ -54,22 +51,11 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { _stakingContract.sinkPendingReward(_validator); } - function endPeriod() external { - _periods.push(block.number); - } - - function periodOf(uint256 _block) external view override returns (uint256 _period) { - for (uint256 _i; _i < _periods.length; _i++) { - if (_block >= _periods[_i]) { - _period = _i + 1; - } - } - } - function submitBlockReward() external payable override {} function wrapUpEpoch() external payable override { _filterUnsatisfiedCandidates(0); + _lastUpdatedPeriod = currentPeriod(); } function getLastUpdatedBlock() external view override returns (uint256) {} @@ -86,8 +72,6 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { function epochEndingAt(uint256 _block) external view override returns (bool) {} - function periodEndingAt(uint256 _block) external view override returns (bool) {} - function slash( address _validatorAddr, uint256 _newJailedUntil, @@ -98,8 +82,6 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { function setNumberOfBlocksInEpoch(uint256 _number) external override {} - function setNumberOfEpochsInPeriod(uint256 _number) external override {} - function maxValidatorNumber() external view override returns (uint256 _maximumValidatorNumber) {} function maxPrioritizedValidatorNumber() @@ -113,10 +95,6 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { return true; } - function numberOfEpochsInPeriod() public view override(CandidateManager, ICandidateManager) returns (uint256) { - return _numberOfEpochsInPeriod; - } - function numberOfBlocksInEpoch() public view override(CandidateManager, ICandidateManager) returns (uint256) { return _numberOfBlocksInEpoch; } @@ -136,4 +114,11 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { } function totalBlockProducers() external view override returns (uint256) {} + function isPeriodEnding() public view virtual returns (bool) { + return currentPeriod() > _lastUpdatedPeriod; + } + + function currentPeriod() public view override(CandidateManager, ICandidateManager) returns (uint256) { + return block.timestamp / 86400; + } } diff --git a/contracts/ronin/Maintenance.sol b/contracts/ronin/Maintenance.sol index 5d2aa7e24..76ea696c3 100644 --- a/contracts/ronin/Maintenance.sol +++ b/contracts/ronin/Maintenance.sol @@ -82,12 +82,6 @@ contract Maintenance is IMaintenance, HasValidatorContract, Initializable { require(_validator.epochEndingAt(_endedAtBlock), "Maintenance: end block is not at the end of an epoch"); Schedule storage _sSchedule = _schedule[_consensusAddr]; - uint256 _period = _validator.periodOf(block.number); - require( - _period > _validator.periodOf(_sSchedule.lastUpdatedBlock) && _period > _validator.periodOf(_sSchedule.to), - "Maintenance: schedule twice in a period is not allowed" - ); - _sSchedule.from = _startedAtBlock; _sSchedule.to = _endedAtBlock; _sSchedule.lastUpdatedBlock = block.number; diff --git a/contracts/ronin/SlashIndicator.sol b/contracts/ronin/SlashIndicator.sol index 5b5367a36..34407e632 100644 --- a/contracts/ronin/SlashIndicator.sol +++ b/contracts/ronin/SlashIndicator.sol @@ -24,8 +24,8 @@ contract SlashIndicator is /// @dev Mapping from validator address => period index => unavailability indicator mapping(address => mapping(uint256 => uint256)) internal _unavailabilityIndicator; - /// @dev Maping from validator address => period index => slash type - mapping(address => mapping(uint256 => SlashType)) internal _unavailabilitySlashed; + /// @dev Mapping from validator address => period index => bridge voting slashed + mapping(address => mapping(uint256 => bool)) internal _bridgeVotingSlashed; /// @dev The last block that a validator is slashed uint256 public lastSlashedBlock; @@ -112,27 +112,15 @@ contract SlashIndicator is return; } - uint256 _period = _validatorContract.periodOf(block.number); + uint256 _period = _validatorContract.currentPeriod(); uint256 _count = ++_unavailabilityIndicator[_validatorAddr][_period]; - (uint256 _misdemeanorThreshold, uint256 _felonyThreshold) = unavailabilityThresholdsOf( - _validatorAddr, - block.number - ); - - SlashType _slashType = getUnavailabilitySlashType(_validatorAddr, _period); - if (_count >= _felonyThreshold && _slashType < SlashType.FELONY) { - _unavailabilitySlashed[_validatorAddr][_period] = SlashType.FELONY; + if (_count == felonyThreshold) { emit UnavailabilitySlashed(_validatorAddr, SlashType.FELONY, _period); _validatorContract.slash(_validatorAddr, block.number + felonyJailDuration, slashFelonyAmount); - return; - } - - if (_count >= _misdemeanorThreshold && _slashType < SlashType.MISDEMEANOR) { - _unavailabilitySlashed[_validatorAddr][_period] = SlashType.MISDEMEANOR; + } else if (_count == misdemeanorThreshold) { emit UnavailabilitySlashed(_validatorAddr, SlashType.MISDEMEANOR, _period); _validatorContract.slash(_validatorAddr, 0, 0); - return; } } @@ -149,8 +137,7 @@ contract SlashIndicator is } if (_pcValidateEvidence(_header1, _header2)) { - uint256 _period = _validatorContract.periodOf(block.number); - _unavailabilitySlashed[_validatorAddr][_period] = SlashType.DOUBLE_SIGNING; + uint256 _period = _validatorContract.currentPeriod(); emit UnavailabilitySlashed(_validatorAddr, SlashType.DOUBLE_SIGNING, _period); _validatorContract.slash(_validatorAddr, doubleSigningJailUntilBlock, slashDoubleSignAmount); } @@ -163,12 +150,9 @@ contract SlashIndicator is IRoninTrustedOrganization.TrustedOrganization memory _org = _roninTrustedOrganizationContract .getTrustedOrganization(_consensusAddr); uint256 _lastVotedBlock = Math.max(_roninGovernanceAdminContract.lastVotedBlock(_org.bridgeVoter), _org.addedBlock); - uint256 _period = _validatorContract.periodOf(block.number); - if ( - block.number - _lastVotedBlock > bridgeVotingThreshold && - _unavailabilitySlashed[_consensusAddr][_period] != SlashType.BRIDGE_VOTING - ) { - _unavailabilitySlashed[_consensusAddr][_period] = SlashType.BRIDGE_VOTING; + uint256 _period = _validatorContract.currentPeriod(); + if (block.number - _lastVotedBlock > bridgeVotingThreshold && !_bridgeVotingSlashed[_consensusAddr][_period]) { + _bridgeVotingSlashed[_consensusAddr][_period] = true; emit UnavailabilitySlashed(_consensusAddr, SlashType.BRIDGE_VOTING, _period); _validatorContract.slash(_consensusAddr, 0, bridgeVotingSlashAmount); } @@ -224,46 +208,8 @@ contract SlashIndicator is // QUERY FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////// - /** - * @inheritdoc ISlashIndicator - */ - function getUnavailabilitySlashType(address _validatorAddr, uint256 _period) public view returns (SlashType) { - return _unavailabilitySlashed[_validatorAddr][_period]; - } - - /** - * @inheritdoc ISlashIndicator - */ - function unavailabilityThresholdsOf(address _addr, uint256 _block) - public - view - returns (uint256 _misdemeanorThreshold, uint256 _felonyThreshold) - { - uint256 _blockLength = _validatorContract.numberOfBlocksInEpoch() * _validatorContract.numberOfEpochsInPeriod(); - uint256 _start = (_block / _blockLength) * _blockLength; - uint256 _end = _start + _blockLength - 1; - IMaintenance.Schedule memory _s = _maintenanceContract.getSchedule(_addr); - - bool _fromInRange = _s.from.inRange(_start, _end); - bool _toInRange = _s.to.inRange(_start, _end); - uint256 _availableDuration = _blockLength; - if (_fromInRange && _toInRange) { - _availableDuration -= _s.to - _s.from + 1; - } else if (_fromInRange) { - _availableDuration -= _end - _s.from + 1; - } else if (_toInRange) { - _availableDuration -= _s.to - _start + 1; - } - - _misdemeanorThreshold = misdemeanorThreshold.scale(_availableDuration, _blockLength); - _felonyThreshold = felonyThreshold.scale(_availableDuration, _blockLength); - } - - /** - * @inheritdoc ISlashIndicator - */ function currentUnavailabilityIndicator(address _validator) external view override returns (uint256) { - return getUnavailabilityIndicator(_validator, _validatorContract.periodOf(block.number)); + return getUnavailabilityIndicator(_validator, _validatorContract.currentPeriod()); } /** diff --git a/contracts/ronin/staking/RewardCalculation.sol b/contracts/ronin/staking/RewardCalculation.sol index ad3dc1bdc..2a3f6557d 100644 --- a/contracts/ronin/staking/RewardCalculation.sol +++ b/contracts/ronin/staking/RewardCalculation.sol @@ -108,7 +108,7 @@ abstract contract RewardCalculation is IRewardPool { _reward.debited = _debited; _reward.credited = _credited; - _reward.lastSyncedPeriod = _periodOf(block.number); + _reward.lastSyncedPeriod = _currentPeriod(); emit PendingRewardUpdated(_poolAddr, _user, _debited, _credited); } @@ -135,7 +135,7 @@ abstract contract RewardCalculation is IRewardPool { emit SettledRewardUpdated(_poolAddr, _user, 0, _sReward.accumulatedRps); _reward.credited += _amount; - _reward.lastSyncedPeriod = _periodOf(block.number); + _reward.lastSyncedPeriod = _currentPeriod(); emit PendingRewardUpdated(_poolAddr, _user, _reward.debited, _reward.credited); } @@ -187,7 +187,7 @@ abstract contract RewardCalculation is IRewardPool { SettledPool storage _sPool = _settledPool[_poolAddr]; if (_accumulatedRpsList[_i] != _sPool.accumulatedRps) { _sPool.accumulatedRps = _accumulatedRpsList[_i]; - _sPool.lastSyncedPeriod = _periodOf(block.number); + _sPool.lastSyncedPeriod = _currentPeriod(); } } emit SettledPoolsUpdated(_poolList, _accumulatedRpsList); @@ -199,7 +199,7 @@ abstract contract RewardCalculation is IRewardPool { function _rewardSinked(address _poolAddr, uint256 _period) internal view virtual returns (bool); /** - * @dev Returns the period from the block number. + * @dev Returns the current period. */ - function _periodOf(uint256 _block) internal view virtual returns (uint256); + function _currentPeriod() internal view virtual returns (uint256); } diff --git a/contracts/ronin/staking/Staking.sol b/contracts/ronin/staking/Staking.sol index fc9f3a23f..7c15a1f4f 100644 --- a/contracts/ronin/staking/Staking.sol +++ b/contracts/ronin/staking/Staking.sol @@ -85,7 +85,7 @@ contract Staking is IStaking, StakingManager, Initializable { * @inheritdoc IStaking */ function sinkPendingReward(address _consensusAddr) external onlyValidatorContract { - uint256 _period = _periodOf(block.number); + uint256 _period = _currentPeriod(); _pRewardSinked[_consensusAddr][_period] = true; _sinkPendingReward(_consensusAddr); } @@ -147,8 +147,8 @@ contract Staking is IStaking, StakingManager, Initializable { /** * @inheritdoc RewardCalculation */ - function _periodOf(uint256 _block) internal view virtual override returns (uint256) { - return IRoninValidatorSet(_validatorContract).periodOf(_block); + function _currentPeriod() internal view virtual override returns (uint256) { + return IRoninValidatorSet(_validatorContract).currentPeriod(); } /** diff --git a/contracts/ronin/validator/CandidateManager.sol b/contracts/ronin/validator/CandidateManager.sol index 6f6bd8bb3..3404de87d 100644 --- a/contracts/ronin/validator/CandidateManager.sol +++ b/contracts/ronin/validator/CandidateManager.sol @@ -64,11 +64,10 @@ abstract contract CandidateManager is ICandidateManager, HasStakingContract { */ function requestRevokeCandidate(address _consensusAddr) external override onlyStakingContract { require(isValidatorCandidate(_consensusAddr), "CandidateManager: query for non-existent candidate"); - uint256 _blockLength = numberOfBlocksInEpoch() * numberOfEpochsInPeriod(); - uint256 _revokedBlock = (block.number / _blockLength) * _blockLength + _blockLength * 2 - 1; - require(_revokedBlock < _candidateInfo[_consensusAddr].revokedBlock, "CandidateManager: invalid block number"); - _candidateInfo[_consensusAddr].revokedBlock = _revokedBlock; - emit CandidateRevokedBlockUpdated(_consensusAddr, _revokedBlock); + uint256 _revokedPeriod = currentPeriod() + 1; + require(_revokedPeriod < _candidateInfo[_consensusAddr].revokedPeriod, "CandidateManager: invalid revoked period"); + _candidateInfo[_consensusAddr].revokedPeriod = _revokedPeriod; + emit CandidateRevokedPeriodUpdated(_consensusAddr, _revokedPeriod); } /** @@ -106,7 +105,7 @@ abstract contract CandidateManager is ICandidateManager, HasStakingContract { /** * @inheritdoc ICandidateManager */ - function numberOfEpochsInPeriod() public view virtual returns (uint256); + function currentPeriod() public view virtual override returns (uint256); /** * @inheritdoc ICandidateManager @@ -127,12 +126,13 @@ abstract contract CandidateManager is ICandidateManager, HasStakingContract { _balances = _staking.totalBalances(_candidates); uint256 _length = _candidates.length; + uint256 _period = currentPeriod(); address[] memory _unsatisfiedCandidates = new address[](_length); uint256 _unsatisfiedCount; address _addr; for (uint _i = 0; _i < _length; _i++) { _addr = _candidates[_i]; - if (_balances[_i] < _minBalance || _candidateInfo[_addr].revokedBlock <= block.number) { + if (_balances[_i] < _minBalance || _candidateInfo[_addr].revokedPeriod <= _period) { _balances[_i] = _balances[--_length]; _unsatisfiedCandidates[_unsatisfiedCount++] = _addr; _removeCandidate(_addr); diff --git a/contracts/ronin/validator/RoninValidatorSet.sol b/contracts/ronin/validator/RoninValidatorSet.sol index e596f2e00..2c60249dc 100644 --- a/contracts/ronin/validator/RoninValidatorSet.sol +++ b/contracts/ronin/validator/RoninValidatorSet.sol @@ -32,14 +32,17 @@ contract RoninValidatorSet is { using EnumFlags for EnumFlags.ValidatorFlag; + /// @dev The total seconds in a day + uint256 internal constant _SECS_IN_DAY = 86400; + /// @dev The maximum number of validator. uint256 internal _maxValidatorNumber; /// @dev The number of blocks in a epoch uint256 internal _numberOfBlocksInEpoch; - /// @dev Returns the number of epochs in a period - uint256 internal _numberOfEpochsInPeriod; /// @dev The last updated block uint256 internal _lastUpdatedBlock; + /// @dev The last updated period + uint256 internal _lastUpdatedPeriod; /// @dev The total of validators uint256 public validatorCount; @@ -96,8 +99,7 @@ contract RoninValidatorSet is uint256 __maxValidatorNumber, uint256 __maxValidatorCandidate, uint256 __maxPrioritizedValidatorNumber, - uint256 __numberOfBlocksInEpoch, - uint256 __numberOfEpochsInPeriod + uint256 __numberOfBlocksInEpoch ) external initializer { _setSlashIndicatorContract(__slashIndicatorContract); _setStakingContract(__stakingContract); @@ -108,7 +110,6 @@ contract RoninValidatorSet is _setMaxValidatorCandidate(__maxValidatorCandidate); _setPrioritizedValidatorNumber(__maxPrioritizedValidatorNumber); _setNumberOfBlocksInEpoch(__numberOfBlocksInEpoch); - _setNumberOfEpochsInPeriod(__numberOfEpochsInPeriod); } /////////////////////////////////////////////////////////////////////////////////////// @@ -127,9 +128,7 @@ contract RoninValidatorSet is address _coinbaseAddr = msg.sender; // Deprecates reward for non-validator or slashed validator if ( - !isBlockProducer(_coinbaseAddr) || - _jailed(_coinbaseAddr) || - _rewardDeprecated(_coinbaseAddr, periodOf(block.number)) + !isBlockProducer(_coinbaseAddr) || _jailed(_coinbaseAddr) || _rewardDeprecated(_coinbaseAddr, currentPeriod()) ) { emit RewardDeprecated(_coinbaseAddr, _submittedReward); return; @@ -138,7 +137,6 @@ contract RoninValidatorSet is (uint256 _validatorStakingVesting, uint256 _bridgeValidatorStakingVesting) = _stakingVestingContract.requestBonus(); uint256 _reward = _submittedReward + _validatorStakingVesting; - IStaking _staking = _stakingContract; uint256 _rate = _candidateInfo[_coinbaseAddr].commissionRate; uint256 _miningAmount = (_rate * _reward) / 100_00; uint256 _delegatingAmount = _reward - _miningAmount; @@ -146,7 +144,7 @@ contract RoninValidatorSet is _miningReward[_coinbaseAddr] += _miningAmount; _delegatingReward[_coinbaseAddr] += _delegatingAmount; _bridgeOperatingReward[_coinbaseAddr] += _bridgeValidatorStakingVesting; - _staking.recordReward(_coinbaseAddr, _delegatingAmount); + _stakingContract.recordReward(_coinbaseAddr, _delegatingAmount); emit BlockRewardSubmitted(_coinbaseAddr, _submittedReward, _validatorStakingVesting); } @@ -160,10 +158,13 @@ contract RoninValidatorSet is ); _lastUpdatedBlock = block.number; + uint256 _newPeriod = _computePeriod(block.timestamp); + bool _periodEnding = _isPeriodEnding(_newPeriod); + _lastUpdatedPeriod = _newPeriod; + address[] memory _currentValidators = getValidators(); uint256 _epoch = epochOf(block.number); - uint256 _period = periodOf(block.number); - bool _periodEnding = periodEndingAt(block.number); + uint256 _period = currentPeriod(); if (_periodEnding) { uint256 _totalDelegatingReward = _distributeRewardToTreasuriesAndCalculateTotalDelegatingReward( @@ -193,7 +194,7 @@ contract RoninValidatorSet is uint256 _newJailedUntil, uint256 _slashAmount ) external onlySlashIndicatorContract { - _rewardDeprecatedAtPeriod[_validatorAddr][periodOf(block.number)] = true; + _rewardDeprecatedAtPeriod[_validatorAddr][currentPeriod()] = true; delete _miningReward[_validatorAddr]; delete _delegatingReward[_validatorAddr]; IStaking(_stakingContract).sinkPendingReward(_validatorAddr); @@ -224,8 +225,9 @@ contract RoninValidatorSet is */ function rewardDeprecated(address[] memory _addrList) external view override returns (bool[] memory _result) { _result = new bool[](_addrList.length); + uint256 _period = currentPeriod(); for (uint256 _i; _i < _addrList.length; _i++) { - _result[_i] = _rewardDeprecated(_addrList[_i], periodOf(block.number)); + _result[_i] = _rewardDeprecated(_addrList[_i], _period); } } @@ -256,10 +258,10 @@ contract RoninValidatorSet is } /** - * @inheritdoc IRoninValidatorSet + * @inheritdoc ICandidateManager */ - function periodOf(uint256 _block) public view virtual override returns (uint256) { - return _block == 0 ? 0 : _block / (_numberOfBlocksInEpoch * _numberOfEpochsInPeriod) + 1; + function currentPeriod() public view virtual override(CandidateManager, ICandidateManager) returns (uint256) { + return _lastUpdatedPeriod; } /** @@ -355,28 +357,15 @@ contract RoninValidatorSet is /** * @inheritdoc IRoninValidatorSet */ - function epochEndingAt(uint256 _block) public view virtual returns (bool) { - return _block % _numberOfBlocksInEpoch == _numberOfBlocksInEpoch - 1; + function isPeriodEnding() external view virtual returns (bool) { + return _isPeriodEnding(_computePeriod(block.timestamp)); } /** * @inheritdoc IRoninValidatorSet */ - function periodEndingAt(uint256 _block) public view virtual returns (bool) { - uint256 _blockLength = _numberOfBlocksInEpoch * _numberOfEpochsInPeriod; - return _block % _blockLength == _blockLength - 1; - } - - /** - * @inheritdoc ICandidateManager - */ - function numberOfEpochsInPeriod() - public - view - override(CandidateManager, ICandidateManager) - returns (uint256 _numberOfEpochs) - { - return _numberOfEpochsInPeriod; + function epochEndingAt(uint256 _block) public view virtual returns (bool) { + return _block % _numberOfBlocksInEpoch == _numberOfBlocksInEpoch - 1; } /** @@ -423,13 +412,6 @@ contract RoninValidatorSet is _setNumberOfBlocksInEpoch(_number); } - /** - * @inheritdoc IRoninValidatorSet - */ - function setNumberOfEpochsInPeriod(uint256 _number) external override onlyAdmin { - _setNumberOfEpochsInPeriod(_number); - } - /////////////////////////////////////////////////////////////////////////////////////// // PRIVATE HELPER FUNCTIONS OF WRAPPING UP EPOCH // /////////////////////////////////////////////////////////////////////////////////////// @@ -663,14 +645,17 @@ contract RoninValidatorSet is } /** - * @dev Updates the number of epochs in period - * - * Emits the event `NumberOfEpochsInPeriodUpdated` - * + * @dev Returns whether the last period is ending when compared with the new period. + */ + function _isPeriodEnding(uint256 _newPeriod) public view virtual returns (bool) { + return _newPeriod > _lastUpdatedPeriod; + } + + /** + * @dev Returns the calculated period. */ - function _setNumberOfEpochsInPeriod(uint256 _number) internal { - _numberOfEpochsInPeriod = _number; - emit NumberOfEpochsInPeriodUpdated(_number); + function _computePeriod(uint256 _timestamp) internal pure returns (uint256) { + return _timestamp / _SECS_IN_DAY; } /** diff --git a/src/config.ts b/src/config.ts index 39042f063..5a14532cc 100644 --- a/src/config.ts +++ b/src/config.ts @@ -101,7 +101,6 @@ export interface RoninValidatorSetArguments { maxValidatorCandidate?: BigNumberish; maxPrioritizedValidatorNumber?: BigNumberish; numberOfBlocksInEpoch?: BigNumberish; - numberOfEpochsInPeriod?: BigNumberish; } export interface RoninValidatorSetConfig { @@ -188,7 +187,6 @@ export const roninValidatorSetConf: RoninValidatorSetConfig = { maxPrioritizedValidatorNumber: 11, maxValidatorCandidate: 100, numberOfBlocksInEpoch: 600, - numberOfEpochsInPeriod: 48, // 1 day }, [Network.Testnet]: undefined, [Network.Mainnet]: undefined, diff --git a/src/deploy/proxy/ronin-validator-proxy.ts b/src/deploy/proxy/ronin-validator-proxy.ts index 5c6324b83..a1edf539f 100644 --- a/src/deploy/proxy/ronin-validator-proxy.ts +++ b/src/deploy/proxy/ronin-validator-proxy.ts @@ -25,7 +25,6 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme roninValidatorSetConf[network.name]!.maxValidatorCandidate, roninValidatorSetConf[network.name]!.maxPrioritizedValidatorNumber, roninValidatorSetConf[network.name]!.numberOfBlocksInEpoch, - roninValidatorSetConf[network.name]!.numberOfEpochsInPeriod, ]); const deployment = await deploy('RoninValidatorSetProxy', { diff --git a/test/helpers/fixture.ts b/test/helpers/fixture.ts index cbd1ac276..c2c3b28fa 100644 --- a/test/helpers/fixture.ts +++ b/test/helpers/fixture.ts @@ -1,6 +1,8 @@ import { BigNumber } from 'ethers'; import { deployments, ethers, network } from 'hardhat'; import { Address } from 'hardhat-deploy/dist/types'; + +import { EpochController } from './ronin-validator-set'; import { MainchainGovernanceAdminArguments, mainchainGovernanceAdminConf, @@ -46,7 +48,6 @@ export const defaultTestConfig = { maxValidatorNumber: 4, maxPrioritizedValidatorNumber: 0, numberOfBlocksInEpoch: 600, - numberOfEpochsInPeriod: 48, minValidatorBalance: BigNumber.from(100), maxValidatorCandidate: 10, @@ -104,7 +105,6 @@ export const initTest = (id: string) => maxPrioritizedValidatorNumber: options?.maxPrioritizedValidatorNumber ?? defaultTestConfig.maxPrioritizedValidatorNumber, numberOfBlocksInEpoch: options?.numberOfBlocksInEpoch ?? defaultTestConfig.numberOfBlocksInEpoch, - numberOfEpochsInPeriod: options?.numberOfEpochsInPeriod ?? defaultTestConfig.numberOfEpochsInPeriod, }; stakingConfig[network.name] = { minValidatorBalance: options?.minValidatorBalance ?? defaultTestConfig.minValidatorBalance, @@ -152,6 +152,7 @@ export const initTest = (id: string) => const stakingContractDeployment = await deployments.get('StakingProxy'); const stakingVestingContractDeployment = await deployments.get('StakingVestingProxy'); const validatorContractDeployment = await deployments.get('RoninValidatorSetProxy'); + await EpochController.setTimestampToPeriodEnding(); return { roninGovernanceAdminAddress: roninGovernanceAdminDeployment.address, diff --git a/test/helpers/ronin-validator-set.ts b/test/helpers/ronin-validator-set.ts index 68df91dcb..8ec3fd5e8 100644 --- a/test/helpers/ronin-validator-set.ts +++ b/test/helpers/ronin-validator-set.ts @@ -11,14 +11,10 @@ const contractInterface = RoninValidatorSet__factory.createInterface(); export class EpochController { readonly minOffset: number; readonly numberOfBlocksInEpoch: number; - readonly numberOfEpochsInPeriod: number; - readonly numberOfBlocksInPeriod: number; - constructor(minOffset: number, numberOfBlocksInEpoch: number, numberOfEpochsInPeriod: number) { + constructor(minOffset: number, numberOfBlocksInEpoch: number) { this.minOffset = minOffset; this.numberOfBlocksInEpoch = numberOfBlocksInEpoch; - this.numberOfEpochsInPeriod = numberOfEpochsInPeriod; - this.numberOfBlocksInPeriod = numberOfBlocksInEpoch * numberOfEpochsInPeriod; } calculateStartOfEpoch(block: number): BigNumber { @@ -31,55 +27,32 @@ export class EpochController { return BigNumber.from(this.numberOfBlocksInEpoch).sub(BigNumber.from(block).mod(this.numberOfBlocksInEpoch)).sub(1); } - diffToEndPeriod(block: BigNumberish): BigNumber { - return BigNumber.from(this.numberOfBlocksInPeriod) - .sub(BigNumber.from(block).mod(this.numberOfBlocksInPeriod)) - .sub(1); - } - calculateEndOfEpoch(block: BigNumberish): BigNumber { return BigNumber.from(block).add(this.diffToEndEpoch(block)); } - calculateEndOfPeriod(block: BigNumberish): BigNumber { - return BigNumber.from(block).add(this.diffToEndPeriod(block)); - } - - calculatePeriodOf(block: BigNumberish): BigNumber { - if (block == 0) { - return BigNumber.from(0); - } - return BigNumber.from(block).div(BigNumber.from(this.numberOfBlocksInPeriod)).add(1); - } - - async currentPeriod(): Promise { - return this.calculatePeriodOf(await ethers.provider.getBlockNumber()); - } - async mineToBeforeEndOfEpoch() { let number = this.diffToEndEpoch(await ethers.provider.getBlockNumber()).sub(1); if (number.lt(0)) { number = number.add(this.numberOfBlocksInEpoch); } - return network.provider.send('hardhat_mine', [ethers.utils.hexStripZeros(number.toHexString())]); + return network.provider.send('hardhat_mine', [ethers.utils.hexStripZeros(number.toHexString()), '0x0']); } - async mineToBeforeEndOfPeriod() { - let number = this.diffToEndPeriod(await ethers.provider.getBlockNumber()).sub(1); - if (number.lt(0)) { - number = number.add(this.numberOfBlocksInPeriod); - } - return network.provider.send('hardhat_mine', [ethers.utils.hexStripZeros(number.toHexString())]); + static async setTimestampToPeriodEnding(): Promise { + const currentDate = new Date(); + const currentTimestamp = currentDate.getTime(); + const nextDate = new Date(currentDate.setDate(currentDate.getDate() + 1)); + const nextBeginningDate = new Date(nextDate.getFullYear(), nextDate.getMonth(), nextDate.getDate()); + const nextBeginningDateTimestamp = nextBeginningDate.getTime(); + await network.provider.send('evm_increaseTime', [ + 86400 + Math.floor((nextBeginningDateTimestamp - currentTimestamp) / 1000), + ]); } async mineToBeginOfNewEpoch() { await this.mineToBeforeEndOfEpoch(); - return network.provider.send('hardhat_mine', ['0x2']); - } - - async mineToBeginOfNewPeriod() { - await this.mineToBeforeEndOfPeriod(); - return network.provider.send('hardhat_mine', ['0x2']); + return network.provider.send('hardhat_mine', ['0x2', '0x0']); } } diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index e946fb5cc..5ece32adb 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -15,7 +15,7 @@ import { RoninGovernanceAdmin__factory, } from '../../src/types'; -import { expects as RoninValidatorSetExpects } from '../helpers/ronin-validator-set'; +import { EpochController, expects as RoninValidatorSetExpects } from '../helpers/ronin-validator-set'; import { expects as CandidateManagerExpects } from '../helpers/candidate-manager'; import { mineBatchTxs } from '../helpers/utils'; import { SlashType } from '../../src/script/slash-indicator'; @@ -39,8 +39,6 @@ const felonyThreshold = 20; const slashFelonyAmount = BigNumber.from(1); const slashDoubleSignAmount = 1000; const minValidatorBalance = BigNumber.from(100); -const numberOfBlocksInEpoch = 600; -const numberOfEpochsInPeriod = 48; describe('[Integration] Slash validators', () => { before(async () => { @@ -76,9 +74,12 @@ describe('[Integration] Slash validators', () => { await mockValidatorLogic.deployed(); await governanceAdminInterface.upgrade(validatorContract.address, mockValidatorLogic.address); - await network.provider.send('hardhat_mine', [ - ethers.utils.hexStripZeros(BigNumber.from(numberOfBlocksInEpoch * numberOfEpochsInPeriod).toHexString()), - ]); + await EpochController.setTimestampToPeriodEnding(); + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + await mineBatchTxs(async () => { + await validatorContract.endEpoch(); + await validatorContract.connect(coinbase).wrapUpEpoch(); + }); }); describe('Slash one validator', async () => { @@ -87,10 +88,7 @@ describe('[Integration] Slash validators', () => { let period: BigNumberish; before(async () => { - const currentBlock = await ethers.provider.getBlockNumber(); - period = await validatorContract.periodOf(currentBlock); - await network.provider.send('hardhat_setCoinbase', [coinbase.address]); - + period = await validatorContract.currentPeriod(); await validatorContract.addValidators([1, 2, 3].map((_) => validatorCandidates[_].address)); }); @@ -130,15 +128,13 @@ describe('[Integration] Slash validators', () => { expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(slasheeInitStakingAmount); + await EpochController.setTimestampToPeriodEnding(); await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); - await validatorContract.connect(coinbase).endPeriod(); wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); - const currentBlock = await ethers.provider.getBlockNumber(); - period = await validatorContract.periodOf(currentBlock); - + period = await validatorContract.currentPeriod(); expectingValidatorSet.push(slashee.address); expectingBlockProducerSet.push(slashee.address); await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(wrapUpEpochTx!, expectingValidatorSet); @@ -198,7 +194,7 @@ describe('[Integration] Slash validators', () => { let _jailUntil = await validatorContract.getJailUntils([slashee.address]); let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber).sub(100); - await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); + await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString(), '0x0']); await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); @@ -212,7 +208,7 @@ describe('[Integration] Slash validators', () => { let _jailUntil = await validatorContract.getJailUntils([slashee.address]); let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber); - await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); + await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString(), '0x0']); await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); @@ -245,15 +241,13 @@ describe('[Integration] Slash validators', () => { expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(slasheeInitStakingAmount); + await EpochController.setTimestampToPeriodEnding(); await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); - await validatorContract.connect(coinbase).endPeriod(); wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); - const currentBlock = await ethers.provider.getBlockNumber(); - period = await validatorContract.periodOf(currentBlock); - + period = await validatorContract.currentPeriod(); expectingValidatorSet.push(slashee.address); await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(wrapUpEpochTx!, expectingValidatorSet); @@ -312,7 +306,7 @@ describe('[Integration] Slash validators', () => { let _jailUntil = await validatorContract.getJailUntils([slashee.address]); let _numOfBlockToEndJailTime = _jailUntil[0].sub(_blockNumber).sub(100); - await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString()]); + await network.provider.send('hardhat_mine', [_numOfBlockToEndJailTime.toHexString(), '0x0']); await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); @@ -346,9 +340,9 @@ describe('[Integration] Slash validators', () => { describe('Check effects on candidate list when the under balance candidates get kicked out', async () => { let expectingRevokedCandidates: Address[]; it('Should the event of updating validator set emitted', async () => { + await EpochController.setTimestampToPeriodEnding(); await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); - await validatorContract.connect(coinbase).endPeriod(); wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index bd526ad04..c01420432 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -17,6 +17,7 @@ import { import { mineBatchTxs } from '../helpers/utils'; import { initTest } from '../helpers/fixture'; import { GovernanceAdminInterface } from '../../src/script/governance-admin-interface'; +import { EpochController } from '../helpers/ronin-validator-set'; let slashContract: SlashIndicator; let stakingContract: Staking; @@ -102,9 +103,10 @@ describe('[Integration] Submit Block Reward', () => { .applyValidatorCandidate(validator.address, validator.address, validator.address, validator.address, 2_00, { value: initStakingAmount, }); + + await EpochController.setTimestampToPeriodEnding(); await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); - await validatorContract.connect(coinbase).endPeriod(); await validatorContract.connect(coinbase).wrapUpEpoch(); }); }); diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index 4cd213c2f..c8dbee5cb 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -14,7 +14,7 @@ import { RoninGovernanceAdmin__factory, } from '../../src/types'; import { expects as StakingExpects } from '../helpers/reward-calculation'; -import { expects as ValidatorSetExpects } from '../helpers/ronin-validator-set'; +import { EpochController, expects as ValidatorSetExpects } from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../helpers/utils'; import { initTest } from '../helpers/fixture'; import { GovernanceAdminInterface } from '../../src/script/governance-admin-interface'; @@ -124,9 +124,9 @@ describe('[Integration] Wrap up epoch', () => { ); } + await EpochController.setTimestampToPeriodEnding(); await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); - await validatorContract.connect(coinbase).endPeriod(); await validatorContract.connect(coinbase).wrapUpEpoch(); }); @@ -152,9 +152,9 @@ describe('[Integration] Wrap up epoch', () => { }); it('Should validator be able to wrap up the epoch', async () => { + await network.provider.send('evm_increaseTime', [86400]); await mineBatchTxs(async () => { await validatorContract.endEpoch(); - await validatorContract.endPeriod(); wrapUpTx = await validatorContract.wrapUpEpoch(); }); }); @@ -191,9 +191,9 @@ describe('[Integration] Wrap up epoch', () => { }); it('Should the ValidatorSet reset counter in SlashIndicator contract', async () => { + await network.provider.send('evm_increaseTime', [86400]); await mineBatchTxs(async () => { await validatorContract.endEpoch(); - await validatorContract.endPeriod(); wrapUpTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); expect( @@ -225,9 +225,9 @@ describe('[Integration] Wrap up epoch', () => { ); } + await EpochController.setTimestampToPeriodEnding(); await mineBatchTxs(async () => { await validatorContract.connect(coinbase).endEpoch(); - await validatorContract.connect(coinbase).endPeriod(); await validatorContract.connect(coinbase).wrapUpEpoch(); }); @@ -272,9 +272,9 @@ describe('[Integration] Wrap up epoch', () => { }); it('Should the validators in the previous epoch (including slashed one) got slashing counter reset, when the epoch ends', async () => { + await network.provider.send('evm_increaseTime', [86400]); await mineBatchTxs(async () => { await validatorContract.endEpoch(); - await validatorContract.endPeriod(); wrapUpTx = await validatorContract.wrapUpEpoch(); }); expect( diff --git a/test/integration/Configuration.test.ts b/test/integration/Configuration.test.ts index ced8701f5..8e8ea47d0 100644 --- a/test/integration/Configuration.test.ts +++ b/test/integration/Configuration.test.ts @@ -37,7 +37,6 @@ const slashDoubleSignAmount = BigNumber.from(10).pow(18).mul(10); const maxValidatorNumber = 4; const maxPrioritizedValidatorNumber = 0; const numberOfBlocksInEpoch = 600; -const numberOfEpochsInPeriod = 48; const minValidatorBalance = BigNumber.from(100); const maxValidatorCandidate = 10; @@ -67,7 +66,6 @@ describe('[Integration] Configuration check', () => { maxValidatorNumber, maxPrioritizedValidatorNumber, numberOfBlocksInEpoch, - numberOfEpochsInPeriod, minValidatorBalance, maxValidatorCandidate, validatorBonusPerBlock, @@ -143,11 +141,6 @@ describe('[Integration] Configuration check', () => { let _numberOfBlocksInEpoch = await validatorContract.numberOfBlocksInEpoch(); expect(_numberOfBlocksInEpoch).to.eq(numberOfBlocksInEpoch); }); - - it('Should config the numberOfEpochsInPeriod correctly', async () => { - let _numberOfEpochsInPeriod = await validatorContract.numberOfEpochsInPeriod(); - expect(_numberOfEpochsInPeriod).to.eq(numberOfEpochsInPeriod); - }); }); describe('StakingContract configuration', async () => { diff --git a/test/maintainance/Maintenance.test.ts b/test/maintainance/Maintenance.test.ts index db5d9bee5..bc571ca24 100644 --- a/test/maintainance/Maintenance.test.ts +++ b/test/maintainance/Maintenance.test.ts @@ -37,7 +37,6 @@ const misdemeanorThreshold = 50; const felonyThreshold = 150; const maxValidatorNumber = 4; const numberOfBlocksInEpoch = 600; -const numberOfEpochsInPeriod = 48; const minValidatorBalance = BigNumber.from(100); const minMaintenanceBlockPeriod = 100; const maxMaintenanceBlockPeriod = 1000; @@ -93,13 +92,9 @@ describe('Maintenance test', () => { } await network.provider.send('hardhat_setCoinbase', [coinbase.address]); - await network.provider.send('hardhat_mine', [ - ethers.utils.hexStripZeros(BigNumber.from(numberOfBlocksInEpoch * numberOfEpochsInPeriod).toHexString()), - ]); - localEpochController = new EpochController(minOffset, numberOfBlocksInEpoch, numberOfEpochsInPeriod); - - await localEpochController.mineToBeforeEndOfPeriod(); + localEpochController = new EpochController(minOffset, numberOfBlocksInEpoch); + await localEpochController.mineToBeforeEndOfEpoch(); let tx = await validatorContract.connect(coinbase).wrapUpEpoch(); await ValidatorSetExpects.emitValidatorSetUpdatedEvent( tx, @@ -276,19 +271,6 @@ describe('Maintenance test', () => { expect(await slashContract.currentUnavailabilityIndicator(validatorCandidates[1].address)).eq(0); }); - it('[Slash Integration] Should the unavailability thresholds of the validator is rescaled', async () => { - currentBlock = await ethers.provider.getBlockNumber(); - const thresholds = await slashContract.unavailabilityThresholdsOf(validatorCandidates[0].address, currentBlock); - - const blockLength = BigNumber.from(numberOfBlocksInEpoch * numberOfEpochsInPeriod); - const diff = blockLength.sub(BigNumber.from(endedAtBlock).sub(startedAtBlock).add(1)); - - expect(thresholds).eql([ - diff.mul(misdemeanorThreshold).div(blockLength), - diff.mul(felonyThreshold).div(blockLength), - ]); - }); - it('Should the validator appear in the block producer list since the maintenance time is ended', async () => { await localEpochController.mineToBeforeEndOfEpoch(); let tx = await validatorContract.connect(coinbase).wrapUpEpoch(); @@ -297,27 +279,7 @@ describe('Maintenance test', () => { expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); }); - it('Should not be able to schedule maintenance twice in a period', async () => { - currentBlock = (await ethers.provider.getBlockNumber()) + 1; - startedAtBlock = localEpochController.calculateStartOfEpoch(currentBlock); - endedAtBlock = localEpochController.calculateEndOfEpoch(startedAtBlock); - await expect( - maintenanceContract - .connect(validatorCandidates[0]) - .schedule(validatorCandidates[0].address, startedAtBlock, endedAtBlock) - ).revertedWith('Maintenance: schedule twice in a period is not allowed'); - }); - - it('[Slash Integration] Should the unavailability thresholds reset in the next period', async () => { - await network.provider.send('hardhat_mine', [ - ethers.utils.hexStripZeros(BigNumber.from(numberOfBlocksInEpoch * numberOfEpochsInPeriod).toHexString()), - ]); - currentBlock = await ethers.provider.getBlockNumber(); - const thresholds = await slashContract.unavailabilityThresholdsOf(validatorCandidates[0].address, currentBlock); - expect(thresholds.map((v) => v.toNumber())).eql([misdemeanorThreshold, felonyThreshold]); - }); - - it('Should be able to schedule in the next period', async () => { + it('Should be able to schedule again in current period when the previous maintenance is done', async () => { currentBlock = (await ethers.provider.getBlockNumber()) + 1; startedAtBlock = localEpochController.calculateStartOfEpoch(currentBlock); endedAtBlock = localEpochController.calculateEndOfEpoch(startedAtBlock); diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index b9eef71f3..e190db35b 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -39,7 +39,6 @@ const felonyThreshold = 10; const maxValidatorNumber = 21; const maxValidatorCandidate = 50; const numberOfBlocksInEpoch = 600; -const numberOfEpochsInPeriod = 48; const minValidatorBalance = BigNumber.from(100); const slashFelonyAmount = BigNumber.from(2); @@ -85,7 +84,6 @@ describe('Slash indicator test', () => { maxValidatorNumber, maxValidatorCandidate, numberOfBlocksInEpoch, - numberOfEpochsInPeriod, minValidatorBalance, slashFelonyAmount, slashDoubleSignAmount, @@ -120,13 +118,9 @@ describe('Slash indicator test', () => { } await network.provider.send('hardhat_setCoinbase', [coinbase.address]); - await network.provider.send('hardhat_mine', [ - ethers.utils.hexStripZeros(BigNumber.from(numberOfBlocksInEpoch * numberOfEpochsInPeriod).toHexString()), - ]); - - localEpochController = new EpochController(minOffset, numberOfBlocksInEpoch, numberOfEpochsInPeriod); - await localEpochController.mineToBeforeEndOfPeriod(); + localEpochController = new EpochController(minOffset, numberOfBlocksInEpoch); + await localEpochController.mineToBeforeEndOfEpoch(); await validatorContract.connect(coinbase).wrapUpEpoch(); expect(await validatorContract.getValidators()).eql(validatorCandidates.map((_) => _.address)); @@ -218,10 +212,10 @@ describe('Slash indicator test', () => { .slash(validatorCandidates[slasheeIdx].address); } - let _period = await localEpochController.currentPeriod(); + let period = await validatorContract.currentPeriod(); await expect(tx) .to.emit(slashContract, 'UnavailabilitySlashed') - .withArgs(validatorCandidates[slasheeIdx].address, SlashType.MISDEMEANOR, _period); + .withArgs(validatorCandidates[slasheeIdx].address, SlashType.MISDEMEANOR, period); setLocalCounterForValidatorAt(slasheeIdx, misdemeanorThreshold); await validateIndicatorAt(slasheeIdx); }); @@ -248,7 +242,7 @@ describe('Slash indicator test', () => { await network.provider.send('hardhat_setCoinbase', [validatorCandidates[slasherIdx].address]); - let _period = await localEpochController.currentPeriod(); + let period = await validatorContract.currentPeriod(); for (let i = 0; i < felonyThreshold; i++) { tx = await slashContract @@ -258,13 +252,13 @@ describe('Slash indicator test', () => { if (i == misdemeanorThreshold - 1) { await expect(tx) .to.emit(slashContract, 'UnavailabilitySlashed') - .withArgs(validatorCandidates[slasheeIdx].address, SlashType.MISDEMEANOR, _period); + .withArgs(validatorCandidates[slasheeIdx].address, SlashType.MISDEMEANOR, period); } } await expect(tx) .to.emit(slashContract, 'UnavailabilitySlashed') - .withArgs(validatorCandidates[slasheeIdx].address, SlashType.FELONY, _period); + .withArgs(validatorCandidates[slasheeIdx].address, SlashType.FELONY, period); setLocalCounterForValidatorAt(slasheeIdx, felonyThreshold); await validateIndicatorAt(slasheeIdx); }); @@ -299,7 +293,9 @@ describe('Slash indicator test', () => { setLocalCounterForValidatorAt(slasheeIdx, numberOfSlashing); await validateIndicatorAt(slasheeIdx); - await localEpochController.mineToBeginOfNewPeriod(); + await EpochController.setTimestampToPeriodEnding(); + await localEpochController.mineToBeforeEndOfEpoch(); + await validatorContract.connect(validatorCandidates[slasherIdx]).wrapUpEpoch(); resetLocalCounterForValidatorAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); @@ -324,7 +320,9 @@ describe('Slash indicator test', () => { await validateIndicatorAt(slasheeIdxs[j]); } - await localEpochController.mineToBeginOfNewPeriod(); + await EpochController.setTimestampToPeriodEnding(); + await localEpochController.mineToBeforeEndOfEpoch(); + await validatorContract.connect(validatorCandidates[slasherIdx]).wrapUpEpoch(); for (let j = 0; j < slasheeIdxs.length; j++) { resetLocalCounterForValidatorAt(slasheeIdxs[j]); @@ -338,7 +336,7 @@ describe('Slash indicator test', () => { let header2: BytesLike; before(async () => { - await network.provider.send('hardhat_mine', [doubleSigningConstrainBlocks.toHexString()]); + await network.provider.send('hardhat_mine', [doubleSigningConstrainBlocks.toHexString(), '0x0']); }); it('Should not be able to slash themselves', async () => { @@ -366,11 +364,11 @@ describe('Slash indicator test', () => { .connect(validatorCandidates[slasherIdx]) .slashDoubleSign(validatorCandidates[slasheeIdx].address, header1, header2); - let _period = await localEpochController.currentPeriod(); + let period = await validatorContract.currentPeriod(); await expect(tx) .to.emit(slashContract, 'UnavailabilitySlashed') - .withArgs(validatorCandidates[slasheeIdx].address, SlashType.DOUBLE_SIGNING, _period); + .withArgs(validatorCandidates[slasheeIdx].address, SlashType.DOUBLE_SIGNING, period); }); }); }); diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index 79513e507..51dea30b9 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -20,7 +20,6 @@ let validatorCandidates: SignerWithAddress[]; const minValidatorBalance = BigNumber.from(20); const maxValidatorCandidate = 50; -const numberOfEpochsInPeriod = 10; const numberOfBlocksInEpoch = 2; describe('Staking test', () => { @@ -35,7 +34,6 @@ describe('Staking test', () => { ethers.constants.AddressZero, stakingVestingContract.address, maxValidatorCandidate, - numberOfEpochsInPeriod, numberOfBlocksInEpoch ); await validatorContract.deployed(); @@ -133,13 +131,15 @@ describe('Staking test', () => { it('Should not be able to request renounce again', async () => { await expect(stakingContract.connect(poolAddr).requestRenounce(poolAddr.address)).revertedWith( - 'CandidateManager: invalid block number' + 'CandidateManager: invalid revoked period' ); }); it('Should the consensus account is no longer be a candidate', async () => { + await network.provider.send('evm_increaseTime', [86400 * 2]); await network.provider.send('hardhat_mine', [ - ethers.utils.hexStripZeros(BigNumber.from(numberOfBlocksInEpoch * numberOfEpochsInPeriod * 2).toHexString()), + ethers.utils.hexStripZeros(BigNumber.from(numberOfBlocksInEpoch).toHexString()), + '0x0', ]); const stakedAmount = minValidatorBalance.mul(2); expect(await stakingContract.getStakingPool(poolAddr.address)).eql([ diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index a9b7658ee..d5295a11f 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -143,9 +143,9 @@ describe('Ronin Validator Set test', () => { let _expectingValidatorsAddr: Address[]; it('Should be able to wrap up epoch at end of period and sync validator set from staking contract', async () => { let tx: ContractTransaction; + await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); - await roninValidatorSet.endPeriod(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); @@ -199,9 +199,9 @@ describe('Ronin Validator Set test', () => { expect((await roninValidatorSet.getValidatorCandidates()).length).eq(localValidatorCandidatesLength + 1); let tx: ContractTransaction; + await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); - await roninValidatorSet.endPeriod(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); currentValidatorSet = [ coinbase.address, @@ -232,9 +232,9 @@ describe('Ronin Validator Set test', () => { it('Should be able to get right reward at the end of period', async () => { const balance = await treasury.getBalance(); let tx: ContractTransaction; + await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); - await roninValidatorSet.endPeriod(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); @@ -278,9 +278,9 @@ describe('Ronin Validator Set test', () => { { const balance = await treasury.getBalance(); await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); - await roninValidatorSet.endPeriod(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); const balanceDiff = (await treasury.getBalance()).sub(balance); @@ -297,9 +297,9 @@ describe('Ronin Validator Set test', () => { let tx: ContractTransaction; const balance = await treasury.getBalance(); await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); - await roninValidatorSet.endPeriod(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); await RoninValidatorSet.expects.emitWrappedUpEpochEvent(tx!); @@ -323,9 +323,9 @@ describe('Ronin Validator Set test', () => { await slashIndicator.slashMisdemeanor(coinbase.address); tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); await RoninValidatorSet.expects.emitRewardDeprecatedEvent(tx!, coinbase.address, 100); + await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); - await roninValidatorSet.endPeriod(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); const balanceDiff = (await treasury.getBalance()).sub(balance); From 7b44d4da7632167c105538875cfb87c7f88a2896 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Mon, 31 Oct 2022 11:09:42 +0700 Subject: [PATCH 179/190] Update bridge reward distribution & add slashing feature for bridge operators (#40) * [BridgeTracking] Add bridge tracking contract * [RoninGatewayV2] Add bridge tracking into the bridge contract * [RoninValidatorSet] Separate reward distribution for bridge operators & block producers * [BridgeTracking] Update bridge tracking contract & update bridge reward distribution * [SlashIndicator] Revamp slashing contract * [Config] rename variables * [Test] Refactor tests --- .../collections/HasBridgeTrackingContract.sol | 46 +++ .../consumers/PercentageConsumer.sol | 6 + contracts/interfaces/IBaseSlash.sol | 18 + contracts/interfaces/IBridgeTracking.sol | 46 +++ contracts/interfaces/IRoninValidatorSet.sol | 65 +++- contracts/interfaces/ISlashBridgeOperator.sol | 56 ++++ contracts/interfaces/ISlashBridgeVoting.sol | 47 +++ contracts/interfaces/ISlashDoubleSign.sol | 54 +++ contracts/interfaces/ISlashIndicator.sol | 149 +-------- contracts/interfaces/ISlashUnavailability.sol | 90 +++++ .../IHasBridgeTrackingContract.sol | 25 ++ .../mocks/MockSlashIndicatorExtended.sol | 2 +- contracts/mocks/MockValidatorSet.sol | 10 +- contracts/ronin/BridgeTracking.sol | 99 ++++++ contracts/ronin/RoninGatewayV2.sol | 65 +++- contracts/ronin/SlashIndicator.sol | 307 ----------------- .../slash-indicator/SlashBridgeOperator.sol | 73 ++++ .../slash-indicator/SlashBridgeVoting.sol | 67 ++++ .../ronin/slash-indicator/SlashDoubleSign.sol | 68 ++++ .../ronin/slash-indicator/SlashIndicator.sol | 78 +++++ .../slash-indicator/SlashUnavailability.sol | 145 ++++++++ .../ronin/validator/CandidateManager.sol | 6 + .../ronin/validator/RoninValidatorSet.sol | 293 ++++++++++------ src/config.ts | 184 +++-------- src/deploy/calculate-address.ts | 10 +- src/deploy/logic/bridge-tracking.ts | 23 ++ src/deploy/mainchain-governance-admin.ts | 12 +- src/deploy/proxy/bonus-reward-proxy.ts | 10 +- src/deploy/proxy/bridge-tracking-proxy.ts | 37 +++ ...nchain-ronin-trusted-organization-proxy.ts | 8 +- src/deploy/proxy/maintenance-proxy.ts | 10 +- .../proxy/ronin-trusted-organization-proxy.ts | 6 +- src/deploy/proxy/ronin-validator-proxy.ts | 20 +- src/deploy/proxy/slash-indicator-proxy.ts | 43 ++- src/deploy/proxy/staking-proxy.ts | 10 +- src/deploy/ronin-governance-admin.ts | 10 +- src/utils.ts | 134 +++++++- test/governance-admin/GovernanceAdmin.test.ts | 25 +- test/helpers/fixture.ts | 204 +++++++----- test/helpers/ronin-validator-set.ts | 47 +-- .../integration/ActionSlashValidators.test.ts | 108 +++--- test/integration/ActionSubmitReward.test.ts | 46 ++- test/integration/ActionWrapUpEpoch.test.ts | 46 ++- test/integration/Configuration.test.ts | 312 ++++++++++-------- test/maintainance/Maintenance.test.ts | 44 ++- test/slash/SlashIndicator.test.ts | 129 +++++--- test/validator/ArrangeValidators.test.ts | 33 +- test/validator/RoninValidatorSet.test.ts | 68 ++-- 48 files changed, 2180 insertions(+), 1214 deletions(-) create mode 100644 contracts/extensions/collections/HasBridgeTrackingContract.sol create mode 100644 contracts/extensions/consumers/PercentageConsumer.sol create mode 100644 contracts/interfaces/IBaseSlash.sol create mode 100644 contracts/interfaces/IBridgeTracking.sol create mode 100644 contracts/interfaces/ISlashBridgeOperator.sol create mode 100644 contracts/interfaces/ISlashBridgeVoting.sol create mode 100644 contracts/interfaces/ISlashDoubleSign.sol create mode 100644 contracts/interfaces/ISlashUnavailability.sol create mode 100644 contracts/interfaces/collections/IHasBridgeTrackingContract.sol create mode 100644 contracts/ronin/BridgeTracking.sol delete mode 100644 contracts/ronin/SlashIndicator.sol create mode 100644 contracts/ronin/slash-indicator/SlashBridgeOperator.sol create mode 100644 contracts/ronin/slash-indicator/SlashBridgeVoting.sol create mode 100644 contracts/ronin/slash-indicator/SlashDoubleSign.sol create mode 100644 contracts/ronin/slash-indicator/SlashIndicator.sol create mode 100644 contracts/ronin/slash-indicator/SlashUnavailability.sol create mode 100644 src/deploy/logic/bridge-tracking.ts create mode 100644 src/deploy/proxy/bridge-tracking-proxy.ts diff --git a/contracts/extensions/collections/HasBridgeTrackingContract.sol b/contracts/extensions/collections/HasBridgeTrackingContract.sol new file mode 100644 index 000000000..c1a9d10cc --- /dev/null +++ b/contracts/extensions/collections/HasBridgeTrackingContract.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./HasProxyAdmin.sol"; +import "../../interfaces/collections/IHasBridgeTrackingContract.sol"; +import "../../interfaces/IBridgeTracking.sol"; + +contract HasBridgeTrackingContract is IHasBridgeTrackingContract, HasProxyAdmin { + IBridgeTracking internal _bridgeTrackingContract; + + modifier onlyBridgeTrackingContract() { + require( + bridgeTrackingContract() == msg.sender, + "HasBridgeTrackingContract: method caller must be bridge tracking contract" + ); + _; + } + + /** + * @inheritdoc IHasBridgeTrackingContract + */ + function bridgeTrackingContract() public view override returns (address) { + return address(_bridgeTrackingContract); + } + + /** + * @inheritdoc IHasBridgeTrackingContract + */ + function setBridgeTrackingContract(address _addr) external virtual override onlyAdmin { + _setBridgeTrackingContract(_addr); + } + + /** + * @dev Sets the bridge tracking contract. + * + * Requirements: + * - The new address is a contract. + * + * Emits the event `BridgeTrackingContractUpdated`. + * + */ + function _setBridgeTrackingContract(address _addr) internal { + _bridgeTrackingContract = IBridgeTracking(_addr); + emit BridgeTrackingContractUpdated(_addr); + } +} diff --git a/contracts/extensions/consumers/PercentageConsumer.sol b/contracts/extensions/consumers/PercentageConsumer.sol new file mode 100644 index 000000000..5a34f0364 --- /dev/null +++ b/contracts/extensions/consumers/PercentageConsumer.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +abstract contract PercentageConsumer { + uint256 internal constant _MAX_PERCENTAGE = 100_00; +} diff --git a/contracts/interfaces/IBaseSlash.sol b/contracts/interfaces/IBaseSlash.sol new file mode 100644 index 000000000..0a697070e --- /dev/null +++ b/contracts/interfaces/IBaseSlash.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IBaseSlash { + enum SlashType { + UNKNOWN, + UNAVAILABILITY_TIER_1, + UNAVAILABILITY_TIER_2, + DOUBLE_SIGNING, + BRIDGE_VOTING, + BRIDGE_OPERATOR_MISSING_VOTE_TIER_1, + BRIDGE_OPERATOR_MISSING_VOTE_TIER_2 + } + + /// @dev Emitted when the validator is slashed. + event Slashed(address indexed validator, SlashType slashType, uint256 period); +} diff --git a/contracts/interfaces/IBridgeTracking.sol b/contracts/interfaces/IBridgeTracking.sol new file mode 100644 index 000000000..6641e6682 --- /dev/null +++ b/contracts/interfaces/IBridgeTracking.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IBridgeTracking { + enum VoteKind { + Deposit, + Withdrawal, + MainchainWithdrawal + } + + /** + * @dev Returns the total number of votes at the specific period `_period`. + */ + function totalVotes(uint256 _period) external view returns (uint256); + + /** + * @dev Returns the total number of ballots at the specific period `_period`. + */ + function totalBallots(uint256 _period) external view returns (uint256); + + /** + * @dev Returns the total number of ballots of bridge operators at the specific period `_period`. + */ + function bulkTotalBallotsOf(uint256 _period, address[] calldata _bridgeOperators) + external + view + returns (uint256[] memory); + + /** + * @dev Returns the total number of ballots of a bridge operator at the specific period `_period`. + */ + function totalBallotsOf(uint256 _period, address _bridgeOperator) external view returns (uint256); + + /** + * @dev Records vote for a receipt and a operator. + * + * Requirements: + * - The method caller is the bridge contract. + * + */ + function recordVote( + VoteKind _kind, + uint256 _requestId, + address _operator + ) external; +} diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index 6496e364c..9bbe7f0b2 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -17,18 +17,52 @@ interface IRoninValidatorSet is ICandidateManager { event BlockProducerSetUpdated(address[]); /// @dev Emitted when the bridge operator set is updated. event BridgeOperatorSetUpdated(address[]); - /// @dev Emitted when the reward of the valdiator is deprecated. - event RewardDeprecated(address coinbaseAddr, uint256 rewardAmount); - /// @dev Emitted when the block reward is submitted. - event BlockRewardSubmitted(address coinbaseAddr, uint256 submittedAmount, uint256 bonusAmount); + /// @dev Emitted when the validator is punished. - event ValidatorPunished(address validatorAddr, uint256 jailedUntil, uint256 deductedStakingAmount); - /// @dev Emitted when the validator reward is distributed. - event MiningRewardDistributed(address indexed validatorAddr, address indexed recipientAddr, uint256 amount); + event ValidatorPunished( + address indexed consensusAddr, + uint256 indexed period, + uint256 jailedUntil, + uint256 deductedStakingAmount, + bool blockProducerRewardDeprecated, + bool bridgeOperatorRewardDeprecated + ); + /// @dev Emitted when the reward of the block producer is deprecated. + event BlockRewardRewardDeprecated(address indexed coinbaseAddr, uint256 rewardAmount); + /// @dev Emitted when the block reward is submitted. + event BlockRewardSubmitted(address indexed coinbaseAddr, uint256 submittedAmount, uint256 bonusAmount); + + /// @dev Emitted when the block producer reward is distributed. + event MiningRewardDistributed(address indexed consensusAddr, address indexed recipient, uint256 amount); + /// @dev Emitted when the contract fails when distributing the block producer reward. + event MiningRewardDistributionFailed( + address indexed consensusAddr, + address indexed recipient, + uint256 amount, + uint256 contractBalance + ); + /// @dev Emitted when the bridge operator reward is distributed. - event BridgeOperatorRewardDistributed(address indexed validatorAddr, address indexed recipientAddr, uint256 amount); - /// @dev Emitted when the amount of RON reward is distributed. + event BridgeOperatorRewardDistributed( + address indexed consensusAddr, + address indexed bridgeOperator, + address indexed recipientAddr, + uint256 amount + ); + /// @dev Emitted when the contract fails when distributing the bridge operator reward. + event BridgeOperatorRewardDistributionFailed( + address indexed consensusAddr, + address indexed bridgeOperator, + address indexed recipient, + uint256 amount, + uint256 contractBalance + ); + + /// @dev Emitted when the amount of RON reward is distributed to staking contract. event StakingRewardDistributed(uint256 amount); + /// @dev Emitted when the contracts fails when distributing the amount of RON to the staking contract. + event StakingRewardDistributionFailed(uint256 amount, uint256 contractBalance); + /// @dev Emitted when the epoch is wrapped up. event WrappedUpEpoch(uint256 indexed periodNumber, uint256 indexed epochNumber, bool periodEnding); @@ -42,7 +76,7 @@ interface IRoninValidatorSet is ICandidateManager { * Requirements: * - The method caller is coinbase. * - * Emits the event `RewardDeprecated` if the coinbase is slashed or no longer be a validator. + * Emits the event `MiningRewardDeprecated` if the coinbase is slashed or no longer be a block producer. * Emits the event `BlockRewardSubmitted` for the valid call. * */ @@ -96,14 +130,17 @@ interface IRoninValidatorSet is ICandidateManager { function jailed(address[] memory) external view returns (bool[] memory); /** - * @dev Returns whether the incoming reward of the validators are deprecated during the current period. + * @dev Returns whether the incoming reward of the block producers are deprecated during the current period. */ - function rewardDeprecated(address[] memory) external view returns (bool[] memory); + function miningRewardDeprecated(address[] memory _blockProducers) external view returns (bool[] memory); /** - * @dev Returns whether the incoming reward of the validators are deprecated during a period. + * @dev Returns whether the incoming reward of the block producers are deprecated during a specific period. */ - function rewardDeprecatedAtPeriod(address[] memory, uint256 _period) external view returns (bool[] memory); + function miningRewardDeprecatedAtPeriod(address[] memory _blockProducers, uint256 _period) + external + view + returns (bool[] memory); /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR NORMAL USER // diff --git a/contracts/interfaces/ISlashBridgeOperator.sol b/contracts/interfaces/ISlashBridgeOperator.sol new file mode 100644 index 000000000..122bfdf38 --- /dev/null +++ b/contracts/interfaces/ISlashBridgeOperator.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface ISlashBridgeOperator { + /** + * @dev Emitted when the configs to slash bridge operator is updated. See the method + * `getBridgeOperatorSlashingConfigs` for param details. + */ + event BridgeOperatorSlashingConfigsUpdated( + uint256 missingVotesRatioTier1, + uint256 missingVotesRatioTier2, + uint256 jailDurationForMissingVotesRatioTier2 + ); + + /** + * @dev Returns the configs related to bridge operator slashing. + * + * @return _missingVotesRatioTier1 The bridge reward will be deprecated if (s)he missed more than this ratio. + * @return _missingVotesRatioTier2 The bridge reward and mining reward will be deprecated and the corresponding + * block producer will be put in jail if (s)he misses more than this ratio. + * @return _jailDurationForMissingVotesRatioTier2 The number of blocks to jail the corresponding block producer when + * its bridge operator is slashed tier-2. + * + */ + function getBridgeOperatorSlashingConfigs() + external + view + returns ( + uint256 _missingVotesRatioTier1, + uint256 _missingVotesRatioTier2, + uint256 _jailDurationForMissingVotesRatioTier2 + ); + + /** + * @dev Sets the configs to slash bridge operators. + * + * Requirements: + * - The method caller is admin. + * + * Emits the event `BridgeOperatorSlashingConfigsUpdated`. + * + * @param _ratioTier1 The bridge reward will be deprecated if (s)he missed more than this ratio. Values 0-10,000 map + * to 0%-100%. + * @param _ratioTier2 The bridge reward and mining reward will be deprecated and the corresponding block producer will + * be put in jail if (s)he misses more than this ratio. Values 0-10,000 map to 0%-100%. + * @param _jailDurationTier2 The number of blocks to jail the corresponding block producer when its bridge operator is + * slashed tier-2. + * + */ + function setBridgeOperatorSlashingConfigs( + uint256 _ratioTier1, + uint256 _ratioTier2, + uint256 _jailDurationTier2 + ) external; +} diff --git a/contracts/interfaces/ISlashBridgeVoting.sol b/contracts/interfaces/ISlashBridgeVoting.sol new file mode 100644 index 000000000..dcf076ad7 --- /dev/null +++ b/contracts/interfaces/ISlashBridgeVoting.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "./IBaseSlash.sol"; + +interface ISlashBridgeVoting is IBaseSlash { + /** + * @dev Emitted when the configs to slash bridge voting is updated. See the method `getBridgeVotingSlashingConfigs` for param + * details. + */ + event BridgeVotingSlashingConfigsUpdated(uint256 bridgeVotingThreshold, uint256 bridgeVotingSlashAmount); + + /** + * @dev Slashes for bridge voter governance. + * + * Emits the event `Slashed`. + */ + function slashBridgeVoting(address _consensusAddr) external; + + /** + * @dev Returns the configs related to bridge voting slashing. + * + * @return _bridgeVotingThreshold The threshold to slash when a trusted organization does not vote for bridge + * operators. + * @return _bridgeVotingSlashAmount The amount of RON to slash bridge voting. + * + */ + function getBridgeVotingSlashingConfigs() + external + view + returns (uint256 _bridgeVotingThreshold, uint256 _bridgeVotingSlashAmount); + + /** + * @dev Sets the configs to slash bridge voting. + * + * Requirements: + * - The method caller is admin. + * + * Emits the event `BridgeVotingSlashingConfigsUpdated`. + * + * @param _threshold The threshold to slash when a trusted organization does not vote for bridge operators. + * @param _slashAmount The amount of RON to slash bridge voting. + * + */ + function setBridgeVotingSlashingConfigs(uint256 _threshold, uint256 _slashAmount) external; +} diff --git a/contracts/interfaces/ISlashDoubleSign.sol b/contracts/interfaces/ISlashDoubleSign.sol new file mode 100644 index 000000000..ed7f6515e --- /dev/null +++ b/contracts/interfaces/ISlashDoubleSign.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "./IBaseSlash.sol"; + +interface ISlashDoubleSign is IBaseSlash { + /** + * @dev Emitted when the configs to slash double sign is updated. See the method `getDoubleSignSlashingConfigs` + * for param details. + */ + event DoubleSignSlashingConfigsUpdated(uint256 slashDoubleSignAmount, uint256 doubleSigningJailUntilBlock); + + /** + * @dev Slashes for double signing. + * + * Requirements: + * - The method caller is coinbase. + * + * Emits the event `Slashed` if the double signing evidence of the two headers valid. + */ + function slashDoubleSign( + address _validatorAddr, + bytes calldata _header1, + bytes calldata _header2 + ) external; + + /** + * @dev Returns the configs related to block producer slashing. + * + * @return _slashDoubleSignAmount The amount of RON to slash double sign. + * @return _doubleSigningJailUntilBlock The block number that the punished validator will be jailed until, due to + * double signing. + * + */ + function getDoubleSignSlashingConfigs() + external + view + returns (uint256 _slashDoubleSignAmount, uint256 _doubleSigningJailUntilBlock); + + /** + * @dev Sets the configs to slash block producers. + * + * Requirements: + * - The method caller is admin. + * + * Emits the event `DoubleSignSlashingConfigsUpdated`. + * + * @param _slashAmount The amount of RON to slash double sign. + * @param _jailUntilBlock The block number that the punished validator will be jailed until, due to double signing. + * + */ + function setDoubleSignSlashingConfigs(uint256 _slashAmount, uint256 _jailUntilBlock) external; +} diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/ISlashIndicator.sol index c179f8d6d..8cfaf831f 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/ISlashIndicator.sol @@ -2,148 +2,9 @@ pragma solidity ^0.8.9; -interface ISlashIndicator { - enum SlashType { - UNKNOWN, - MISDEMEANOR, - FELONY, - DOUBLE_SIGNING, - BRIDGE_VOTING - } +import "./ISlashDoubleSign.sol"; +import "./ISlashBridgeVoting.sol"; +import "./ISlashBridgeOperator.sol"; +import "./ISlashUnavailability.sol"; - /// @dev Emitted when the validator is slashed for unavailability - event UnavailabilitySlashed(address indexed validator, SlashType slashType, uint256 period); - /// @dev Emitted when the thresholds updated - event SlashThresholdsUpdated(uint256 felonyThreshold, uint256 misdemeanorThreshold); - /// @dev Emitted when the threshold to slash when trusted organization does not vote for bridge operators is updated - event BridgeVotingThresholdUpdated(uint256 threshold); - /// @dev Emitted when the amount of RON to slash bridge voting is updated - event BridgeVotingSlashAmountUpdated(uint256 amount); - /// @dev Emitted when the amount of slashing felony updated - event SlashFelonyAmountUpdated(uint256 slashFelonyAmount); - /// @dev Emitted when the amount of slashing double sign updated - event SlashDoubleSignAmountUpdated(uint256 slashDoubleSignAmount); - /// @dev Emiited when the duration of jailing felony updated - event FelonyJailDurationUpdated(uint256 felonyJailDuration); - /// @dev Emiited when the constrain of ahead block in double signing updated - event DoubleSigningConstrainBlocksUpdated(uint256 doubleSigningConstrainBlocks); - /// @dev Emiited when the block number to jail the double signing validator to is updated - event DoubleSigningJailUntilBlockUpdated(uint256 doubleSigningJailUntilBlock); - - /** - * @dev Slashes for unavailability by increasing the counter of validator with `_valAddr`. - * If the counter passes the threshold, call the function from the validator contract. - * - * Requirements: - * - Only coinbase can call this method - * - * Emits the event `UnavailabilitySlashed` when the threshold is reached. - * - */ - function slash(address _valAddr) external; - - /** - * @dev Slashes for double signing - * - * Requirements: - * - Only coinbase can call this method - * - * Emits the event `UnavailabilitySlashed` if the double signing evidence of the two headers valid - */ - function slashDoubleSign( - address _validatorAddr, - bytes calldata _header1, - bytes calldata _header2 - ) external; - - /** - * @dev Slashes for bridge voter governance. - * - * Emits the event `UnavailabilitySlashed`. - */ - function slashBridgeVoting(address _consensusAddr) external; - - /** - * @dev Sets the slash thresholds - * - * Requirements: - * - Only admin can call this method - * - * Emits the event `SlashThresholdsUpdated` - * - */ - function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external; - - /** - * @dev Sets the slash felony amount - * - * Requirements: - * - Only admin can call this method - * - * Emits the event `SlashFelonyAmountUpdated` - * - */ - function setSlashFelonyAmount(uint256 _slashFelonyAmount) external; - - /** - * @dev Sets the slash double sign amount - * - * Requirements: - * - Only admin can call this method - * - * Emits the event `SlashDoubleSignAmountUpdated` - * - */ - function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external; - - /** - * @dev Sets the felony jail duration - * - * Requirements: - * - Only admin can call this method - * - * Emits the event `FelonyJailDurationUpdated` - * - */ - function setFelonyJailDuration(uint256 _felonyJailDuration) external; - - /** - * @dev Sets the threshold to slash when trusted organization does not vote for bridge operators. - * - * Requirements: - * - Only admin can call this method - * - * Emits the event `BridgeVotingThresholdUpdated` - * - */ - function setBridgeVotingThreshold(uint256 _threshold) external; - - /** - * @dev Sets the amount of RON to slash bridge voting. - * - * Requirements: - * - Only admin can call this method - * - * Emits the event `BridgeVotingSlashAmountUpdated` - * - */ - function setBridgeVotingSlashAmount(uint256 _amount) external; - - /** - * @dev Returns the current unavailability indicator of a validator. - */ - function currentUnavailabilityIndicator(address _validator) external view returns (uint256); - - /** - * @dev Retursn the unavailability indicator in the period `_period` of a validator. - */ - function getUnavailabilityIndicator(address _validator, uint256 _period) external view returns (uint256); - - /** - * @dev Gets the unavailability thresholds. - */ - function getUnavailabilityThresholds() - external - view - returns (uint256 _misdemeanorThreshold, uint256 _felonyThreshold); -} +interface ISlashIndicator is ISlashDoubleSign, ISlashBridgeVoting, ISlashBridgeOperator, ISlashUnavailability {} diff --git a/contracts/interfaces/ISlashUnavailability.sol b/contracts/interfaces/ISlashUnavailability.sol new file mode 100644 index 000000000..2f1579af2 --- /dev/null +++ b/contracts/interfaces/ISlashUnavailability.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "./IBaseSlash.sol"; + +interface ISlashUnavailability is IBaseSlash { + /** + * @dev Emitted when the configs to slash bridge operator is updated. See the method `getUnavailabilitySlashingConfigs` + * for param details. + */ + event UnavailabilitySlashingConfigsUpdated( + uint256 unavailabilityTier1Threshold, + uint256 unavailabilityTier2Threshold, + uint256 slashAmountForUnavailabilityTier2Threshold, + uint256 jailDurationForUnavailabilityTier2Threshold + ); + + /** + * @dev Returns the last block that a block producer is slashed for unavailability. + */ + function lastUnavailabilitySlashedBlock() external view returns (uint256); + + /** + * @dev Slashes for unavailability by increasing the counter of block producer `_consensusAddr`. + * + * Requirements: + * - The method caller is coinbase. + * + * Emits the event `Slashed` when the threshold is reached. + * + */ + function slashUnavailability(address _consensusAddr) external; + + /** + * @dev Returns the current unavailability indicator of a block producer. + */ + function currentUnavailabilityIndicator(address _validator) external view returns (uint256); + + /** + * @dev Retursn the unavailability indicator in the period `_period` of a block producer. + */ + function getUnavailabilityIndicator(address _validator, uint256 _period) external view returns (uint256); + + /** + * @dev Returns the configs related to block producer slashing. + * + * @return _unavailabilityTier1Threshold The mining reward will be deprecated, if (s)he missed more than this + * threshold. + * @return _unavailabilityTier2Threshold The mining reward will be deprecated, (s)he will be put in jailed, and will + * be deducted self-staking if (s)he misses more than this threshold. + * @return _slashAmountForUnavailabilityTier2Threshold The amount of RON to deduct from self-staking of a block + * producer when (s)he is slashed tier-2. + * @return _jailDurationForUnavailabilityTier2Threshold The number of blocks to jail a block producer when (s)he is + * slashed tier-2. + * + */ + function getUnavailabilitySlashingConfigs() + external + view + returns ( + uint256 _unavailabilityTier1Threshold, + uint256 _unavailabilityTier2Threshold, + uint256 _slashAmountForUnavailabilityTier2Threshold, + uint256 _jailDurationForUnavailabilityTier2Threshold + ); + + /** + * @dev Sets the configs to slash block producers. + * + * Requirements: + * - The method caller is admin. + * + * Emits the event `BridgeOperatorSlashingConfigsUpdated`. + * + * @param _tier1Threshold The mining reward will be deprecated, if (s)he missed more than this threshold. + * @param _tier2Threshold The mining reward will be deprecated, (s)he will be put in jailed, and will be deducted + * self-staking if (s)he misses more than this threshold. + * @param _slashAmountForTier2Threshold The amount of RON to deduct from self-staking of a block producer when (s)he + * is slashed tier-2. + * @param _jailDurationForTier2Threshold The number of blocks to jail a block producer when (s)he is slashed tier-2. + * + */ + function setUnavailabilitySlashingConfigs( + uint256 _tier1Threshold, + uint256 _tier2Threshold, + uint256 _slashAmountForTier2Threshold, + uint256 _jailDurationForTier2Threshold + ) external; +} diff --git a/contracts/interfaces/collections/IHasBridgeTrackingContract.sol b/contracts/interfaces/collections/IHasBridgeTrackingContract.sol new file mode 100644 index 000000000..e2811bd35 --- /dev/null +++ b/contracts/interfaces/collections/IHasBridgeTrackingContract.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IHasBridgeTrackingContract { + /// @dev Emitted when the bridge tracking contract is updated. + event BridgeTrackingContractUpdated(address); + + /** + * @dev Returns the bridge tracking contract. + */ + function bridgeTrackingContract() external view returns (address); + + /** + * @dev Sets the bridge tracking contract. + * + * Requirements: + * - The method caller is admin. + * - The new address is a contract. + * + * Emits the event `BridgeTrackingContractUpdated`. + * + */ + function setBridgeTrackingContract(address) external; +} diff --git a/contracts/mocks/MockSlashIndicatorExtended.sol b/contracts/mocks/MockSlashIndicatorExtended.sol index 696a53662..291030729 100644 --- a/contracts/mocks/MockSlashIndicatorExtended.sol +++ b/contracts/mocks/MockSlashIndicatorExtended.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.9; import "./MockPrecompile.sol"; -import "../ronin/SlashIndicator.sol"; +import "../ronin/slash-indicator/SlashIndicator.sol"; contract MockSlashIndicatorExtended is SlashIndicator, MockPrecompile { function slashFelony(address _validatorAddr) external { diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index d2cecb918..af6304935 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -62,9 +62,14 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { function jailed(address[] memory) external view override returns (bool[] memory) {} - function rewardDeprecatedAtPeriod(address[] memory, uint256 _period) external view override returns (bool[] memory) {} + function miningRewardDeprecatedAtPeriod(address[] memory, uint256 _period) + external + view + override + returns (bool[] memory) + {} - function rewardDeprecated(address[] memory) external view override returns (bool[] memory) {} + function miningRewardDeprecated(address[] memory) external view override returns (bool[] memory) {} function epochOf(uint256 _block) external view override returns (uint256) {} @@ -114,6 +119,7 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { } function totalBlockProducers() external view override returns (uint256) {} + function isPeriodEnding() public view virtual returns (bool) { return currentPeriod() > _lastUpdatedPeriod; } diff --git a/contracts/ronin/BridgeTracking.sol b/contracts/ronin/BridgeTracking.sol new file mode 100644 index 000000000..c39c1ecdc --- /dev/null +++ b/contracts/ronin/BridgeTracking.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "../extensions/collections/HasBridgeContract.sol"; +import "../extensions/collections/HasValidatorContract.sol"; +import "../interfaces/IBridgeTracking.sol"; + +contract BridgeTracking is HasBridgeContract, HasValidatorContract, Initializable, IBridgeTracking { + /// @dev Mapping from period number => total number of all votes + mapping(uint256 => uint256) internal _totalVotes; + /// @dev Mapping from period number => total number of all ballots + mapping(uint256 => uint256) internal _totalBallots; + /// @dev Mapping from period number => bridge operator address => total number of ballots + mapping(uint256 => mapping(address => uint256)) internal _totalBallotsOf; + + /// @dev Mapping from vote kind => request id => flag indicating whether the receipt is recorded or not + mapping(VoteKind => mapping(uint256 => bool)) _receiptRecorded; + /// @dev Mapping from vote kind => request id => bridge operator address => flag indicating whether the operator voted or not + mapping(VoteKind => mapping(uint256 => mapping(address => bool))) internal _receiptVoted; + + /// @dev The block that the contract allows incoming mutable calls. + uint256 public startedAtBlock; + + /** + * @dev Initializes the contract storage. + */ + function initialize( + address _bridgeContract, + address _validatorContract, + uint256 _startedAtBlock + ) external initializer { + _setBridgeContract(_bridgeContract); + _setValidatorContract(_validatorContract); + startedAtBlock = _startedAtBlock; + } + + /** + * @inheritdoc IBridgeTracking + */ + function totalVotes(uint256 _period) external view override returns (uint256) { + return _totalVotes[_period]; + } + + /** + * @inheritdoc IBridgeTracking + */ + function totalBallots(uint256 _period) external view override returns (uint256) { + return _totalBallots[_period]; + } + + /** + * @inheritdoc IBridgeTracking + */ + function bulkTotalBallotsOf(uint256 _period, address[] calldata _bridgeOperators) + external + view + override + returns (uint256[] memory _res) + { + _res = new uint256[](_bridgeOperators.length); + for (uint _i = 0; _i < _bridgeOperators.length; _i++) { + _res[_i] = totalBallotsOf(_period, _bridgeOperators[_i]); + } + } + + /** + * @inheritdoc IBridgeTracking + */ + function totalBallotsOf(uint256 _period, address _bridgeOperator) public view override returns (uint256) { + return _totalBallotsOf[_period][_bridgeOperator]; + } + + /** + * @inheritdoc IBridgeTracking + */ + function recordVote( + VoteKind _kind, + uint256 _requestId, + address _operator + ) external override onlyBridgeContract { + if (block.number < startedAtBlock) { + return; + } + + uint256 _period = _validatorContract.currentPeriod(); + if (!_receiptRecorded[_kind][_requestId]) { + _totalVotes[_period]++; + _receiptRecorded[_kind][_requestId] = true; + } + + if (!_receiptVoted[_kind][_requestId][_operator]) { + _totalBallots[_period]++; + _totalBallotsOf[_period][_operator]++; + _receiptVoted[_kind][_requestId][_operator] = true; + } + } +} diff --git a/contracts/ronin/RoninGatewayV2.sol b/contracts/ronin/RoninGatewayV2.sol index 3b007242a..25ccd9e40 100644 --- a/contracts/ronin/RoninGatewayV2.sol +++ b/contracts/ronin/RoninGatewayV2.sol @@ -10,7 +10,9 @@ import "../interfaces/IERC20Mintable.sol"; import "../interfaces/IERC721Mintable.sol"; import "../interfaces/IRoninGatewayV2.sol"; import "../interfaces/IRoninValidatorSet.sol"; +import "../interfaces/IBridgeTracking.sol"; import "../interfaces/collections/IHasValidatorContract.sol"; +import "../interfaces/collections/IHasBridgeTrackingContract.sol"; contract RoninGatewayV2 is GatewayV2, @@ -19,7 +21,8 @@ contract RoninGatewayV2 is MinimumWithdrawal, AccessControlEnumerable, IRoninGatewayV2, - IHasValidatorContract + IHasValidatorContract, + IHasBridgeTrackingContract { using Token for Token.Info; using Transfer for Transfer.Request; @@ -43,7 +46,10 @@ contract RoninGatewayV2 is /// @dev Mapping from token address => chain id => mainchain token address mapping(address => mapping(uint256 => MappedToken)) internal _mainchainToken; + /// @dev The ronin validator contract IRoninValidatorSet internal _validatorContract; + /// @dev The bridge tracking contract + IBridgeTracking internal _bridgeTrackingContract; fallback() external payable { _fallback(); @@ -96,17 +102,17 @@ contract RoninGatewayV2 is } /** - * @dev Sets the validator contract. - * - * Requirements: - * - The new address is a contract. - * - * Emits the event `ValidatorContractUpdated`. - * + * @inheritdoc IHasBridgeTrackingContract */ - function _setValidatorContract(address _addr) internal { - _validatorContract = IRoninValidatorSet(_addr); - emit ValidatorContractUpdated(_addr); + function bridgeTrackingContract() external view override returns (address) { + return address(_bridgeTrackingContract); + } + + /** + * @inheritdoc IHasBridgeTrackingContract + */ + function setBridgeTrackingContract(address _addr) external override onlyAdmin { + _setBridgeTrackingContract(_addr); } /** @@ -163,6 +169,7 @@ contract RoninGatewayV2 is address _sender = msg.sender; uint256 _weight = _getValidatorWeight(_sender); _depositFor(_receipt, _sender, _weight, minimumVoteWeight()); + _bridgeTrackingContract.recordVote(IBridgeTracking.VoteKind.Deposit, _receipt.id, _sender); } /** @@ -180,6 +187,7 @@ contract RoninGatewayV2 is _executedReceipts = new bool[](_withdrawalIds.length); for (uint256 _i; _i < _withdrawalIds.length; _i++) { _withdrawalId = _withdrawalIds[_i]; + _bridgeTrackingContract.recordVote(IBridgeTracking.VoteKind.MainchainWithdrawal, _withdrawalId, _governor); if (mainchainWithdrew(_withdrawalId)) { _executedReceipts[_i] = true; } else { @@ -207,6 +215,7 @@ contract RoninGatewayV2 is uint256 _minVoteWeight = minimumVoteWeight(); for (uint256 _i; _i < _receipts.length; _i++) { _receipt = _receipts[_i]; + _bridgeTrackingContract.recordVote(IBridgeTracking.VoteKind.Deposit, _receipt.id, _sender); if (depositVote[_receipt.mainchain.chainId][_receipt.id].status == VoteStatus.Executed) { _executedReceipts[_i] = true; } else { @@ -254,8 +263,12 @@ contract RoninGatewayV2 is _withdrawals.length > 0 && _withdrawals.length == _signatures.length, "RoninGatewayV2: invalid array length" ); + + uint256 _id; for (uint256 _i; _i < _withdrawals.length; _i++) { - _withdrawalSig[_withdrawals[_i]][_validator] = _signatures[_i]; + _id = _withdrawals[_i]; + _withdrawalSig[_id][_validator] = _signatures[_i]; + _bridgeTrackingContract.recordVote(IBridgeTracking.VoteKind.Withdrawal, _id, _validator); } } @@ -435,4 +448,32 @@ contract RoninGatewayV2 is function _getTotalWeight() internal view virtual override returns (uint256) { return _validatorContract.totalBridgeOperators(); } + + /** + * @dev Sets the validator contract. + * + * Requirements: + * - The new address is a contract. + * + * Emits the event `ValidatorContractUpdated`. + * + */ + function _setValidatorContract(address _addr) internal { + _validatorContract = IRoninValidatorSet(_addr); + emit ValidatorContractUpdated(_addr); + } + + /** + * @dev Sets the bridge tracking contract. + * + * Requirements: + * - The new address is a contract. + * + * Emits the event `BridgeTrackingContractUpdated`. + * + */ + function _setBridgeTrackingContract(address _addr) internal { + _bridgeTrackingContract = IBridgeTracking(_addr); + emit BridgeTrackingContractUpdated(_addr); + } } diff --git a/contracts/ronin/SlashIndicator.sol b/contracts/ronin/SlashIndicator.sol deleted file mode 100644 index 34407e632..000000000 --- a/contracts/ronin/SlashIndicator.sol +++ /dev/null @@ -1,307 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "../interfaces/ISlashIndicator.sol"; -import "../extensions/collections/HasValidatorContract.sol"; -import "../extensions/collections/HasMaintenanceContract.sol"; -import "../extensions/collections/HasRoninTrustedOrganizationContract.sol"; -import "../extensions/collections/HasRoninGovernanceAdminContract.sol"; -import "../libraries/Math.sol"; -import "../precompile-usages/PrecompileUsageValidateDoubleSign.sol"; - -contract SlashIndicator is - ISlashIndicator, - PrecompileUsageValidateDoubleSign, - HasValidatorContract, - HasMaintenanceContract, - HasRoninTrustedOrganizationContract, - HasRoninGovernanceAdminContract, - Initializable -{ - using Math for uint256; - - /// @dev Mapping from validator address => period index => unavailability indicator - mapping(address => mapping(uint256 => uint256)) internal _unavailabilityIndicator; - /// @dev Mapping from validator address => period index => bridge voting slashed - mapping(address => mapping(uint256 => bool)) internal _bridgeVotingSlashed; - - /// @dev The last block that a validator is slashed - uint256 public lastSlashedBlock; - - /// @dev The number of blocks that the current block can be ahead of the double signed blocks - uint256 public doubleSigningConstrainBlocks; - - /// @dev The threshold to slash when validator is unavailability reaches misdemeanor - uint256 public misdemeanorThreshold; - /// @dev The threshold to slash when validator is unavailability reaches felony - uint256 public felonyThreshold; - /// @dev The threshold to slash when a trusted organization does not vote for bridge operators - uint256 public bridgeVotingThreshold; - - /// @dev The amount of RON to slash felony. - uint256 public slashFelonyAmount; - /// @dev The amount of RON to slash double sign. - uint256 public slashDoubleSignAmount; - /// @dev The amount of RON to slash bridge voting. - uint256 public bridgeVotingSlashAmount; - /// @dev The block duration to jail a validator that reaches felony thresold. - uint256 public felonyJailDuration; - /// @dev The block number that the punished validator will be jailed until, due to double signing. - uint256 public doubleSigningJailUntilBlock; - - modifier onlyCoinbase() { - require(msg.sender == block.coinbase, "SlashIndicator: method caller must be coinbase"); - _; - } - - modifier oncePerBlock() { - require( - block.number > lastSlashedBlock, - "SlashIndicator: cannot slash a validator twice or slash more than one validator in one block" - ); - _; - lastSlashedBlock = block.number; - } - - constructor() { - _disableInitializers(); - } - - /** - * @dev Initializes the contract storage. - */ - function initialize( - address __validatorContract, - address __maintenanceContract, - address __roninTrustedOrganizationContract, - address __roninGovernanceAdminContract, - uint256 _misdemeanorThreshold, - uint256 _felonyThreshold, - uint256 _bridgeVotingThreshold, - uint256 _slashFelonyAmount, - uint256 _slashDoubleSignAmount, - uint256 _bridgeVotingSlashAmount, - uint256 _felonyJailBlocks, - uint256 _doubleSigningConstrainBlocks - ) external initializer { - _setValidatorContract(__validatorContract); - _setMaintenanceContract(__maintenanceContract); - _setRoninTrustedOrganizationContract(__roninTrustedOrganizationContract); - _setRoninGovernanceAdminContract(__roninGovernanceAdminContract); - _setSlashThresholds(_felonyThreshold, _misdemeanorThreshold); - _setBridgeVotingThreshold(_bridgeVotingThreshold); - _setSlashFelonyAmount(_slashFelonyAmount); - _setSlashDoubleSignAmount(_slashDoubleSignAmount); - _setBridgeVotingSlashAmount(_bridgeVotingSlashAmount); - _setFelonyJailDuration(_felonyJailBlocks); - _setDoubleSigningConstrainBlocks(_doubleSigningConstrainBlocks); - _setDoubleSigningJailUntilBlock(type(uint256).max); - } - - /////////////////////////////////////////////////////////////////////////////////////// - // SLASHING FUNCTIONS // - /////////////////////////////////////////////////////////////////////////////////////// - - /** - * @inheritdoc ISlashIndicator - */ - function slash(address _validatorAddr) external override onlyCoinbase oncePerBlock { - if (!_shouldSlash(_validatorAddr)) { - return; - } - - uint256 _period = _validatorContract.currentPeriod(); - uint256 _count = ++_unavailabilityIndicator[_validatorAddr][_period]; - - if (_count == felonyThreshold) { - emit UnavailabilitySlashed(_validatorAddr, SlashType.FELONY, _period); - _validatorContract.slash(_validatorAddr, block.number + felonyJailDuration, slashFelonyAmount); - } else if (_count == misdemeanorThreshold) { - emit UnavailabilitySlashed(_validatorAddr, SlashType.MISDEMEANOR, _period); - _validatorContract.slash(_validatorAddr, 0, 0); - } - } - - /** - * @inheritdoc ISlashIndicator - */ - function slashDoubleSign( - address _validatorAddr, - bytes calldata _header1, - bytes calldata _header2 - ) external override onlyCoinbase oncePerBlock { - if (!_shouldSlash(_validatorAddr)) { - return; - } - - if (_pcValidateEvidence(_header1, _header2)) { - uint256 _period = _validatorContract.currentPeriod(); - emit UnavailabilitySlashed(_validatorAddr, SlashType.DOUBLE_SIGNING, _period); - _validatorContract.slash(_validatorAddr, doubleSigningJailUntilBlock, slashDoubleSignAmount); - } - } - - /** - * @inheritdoc ISlashIndicator - */ - function slashBridgeVoting(address _consensusAddr) external { - IRoninTrustedOrganization.TrustedOrganization memory _org = _roninTrustedOrganizationContract - .getTrustedOrganization(_consensusAddr); - uint256 _lastVotedBlock = Math.max(_roninGovernanceAdminContract.lastVotedBlock(_org.bridgeVoter), _org.addedBlock); - uint256 _period = _validatorContract.currentPeriod(); - if (block.number - _lastVotedBlock > bridgeVotingThreshold && !_bridgeVotingSlashed[_consensusAddr][_period]) { - _bridgeVotingSlashed[_consensusAddr][_period] = true; - emit UnavailabilitySlashed(_consensusAddr, SlashType.BRIDGE_VOTING, _period); - _validatorContract.slash(_consensusAddr, 0, bridgeVotingSlashAmount); - } - } - - /////////////////////////////////////////////////////////////////////////////////////// - // GOVERNANCE FUNCTIONS // - /////////////////////////////////////////////////////////////////////////////////////// - - /** - * @inheritdoc ISlashIndicator - */ - function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external override onlyAdmin { - _setSlashThresholds(_felonyThreshold, _misdemeanorThreshold); - } - - /** - * @inheritdoc ISlashIndicator - */ - function setSlashFelonyAmount(uint256 _slashFelonyAmount) external override onlyAdmin { - _setSlashFelonyAmount(_slashFelonyAmount); - } - - /** - * @inheritdoc ISlashIndicator - */ - function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external override onlyAdmin { - _setSlashDoubleSignAmount(_slashDoubleSignAmount); - } - - /** - * @inheritdoc ISlashIndicator - */ - function setFelonyJailDuration(uint256 _felonyJailDuration) external override onlyAdmin { - _setFelonyJailDuration(_felonyJailDuration); - } - - /** - * @inheritdoc ISlashIndicator - */ - function setBridgeVotingThreshold(uint256 _threshold) external override onlyAdmin { - _setBridgeVotingThreshold(_threshold); - } - - /** - * @inheritdoc ISlashIndicator - */ - function setBridgeVotingSlashAmount(uint256 _amount) external override onlyAdmin { - _setBridgeVotingSlashAmount(_amount); - } - - /////////////////////////////////////////////////////////////////////////////////////// - // QUERY FUNCTIONS // - /////////////////////////////////////////////////////////////////////////////////////// - - function currentUnavailabilityIndicator(address _validator) external view override returns (uint256) { - return getUnavailabilityIndicator(_validator, _validatorContract.currentPeriod()); - } - - /** - * @inheritdoc ISlashIndicator - */ - function getUnavailabilityThresholds() external view override returns (uint256, uint256) { - return (misdemeanorThreshold, felonyThreshold); - } - - /** - * @inheritdoc ISlashIndicator - */ - function getUnavailabilityIndicator(address _validator, uint256 _period) public view override returns (uint256) { - return _unavailabilityIndicator[_validator][_period]; - } - - /////////////////////////////////////////////////////////////////////////////////////// - // HELPER FUNCTIONS // - /////////////////////////////////////////////////////////////////////////////////////// - - /** - * @dev Sets the slash thresholds - */ - function _setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) internal { - felonyThreshold = _felonyThreshold; - misdemeanorThreshold = _misdemeanorThreshold; - emit SlashThresholdsUpdated(_felonyThreshold, _misdemeanorThreshold); - } - - /** - * @dev Sets the slash felony amount - */ - function _setSlashFelonyAmount(uint256 _slashFelonyAmount) internal { - slashFelonyAmount = _slashFelonyAmount; - emit SlashFelonyAmountUpdated(_slashFelonyAmount); - } - - /** - * @dev Sets the slash double sign amount - */ - function _setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) internal { - slashDoubleSignAmount = _slashDoubleSignAmount; - emit SlashDoubleSignAmountUpdated(_slashDoubleSignAmount); - } - - /** - * @dev Sets the felony jail duration - */ - function _setFelonyJailDuration(uint256 _felonyJailDuration) internal { - felonyJailDuration = _felonyJailDuration; - emit FelonyJailDurationUpdated(_felonyJailDuration); - } - - /** - * @dev Sets the double signing constrain blocks - */ - function _setDoubleSigningConstrainBlocks(uint256 _doubleSigningConstrainBlocks) internal { - doubleSigningConstrainBlocks = _doubleSigningConstrainBlocks; - emit DoubleSigningConstrainBlocksUpdated(_doubleSigningConstrainBlocks); - } - - /** - * @dev Sets the double signing jail until block number - */ - function _setDoubleSigningJailUntilBlock(uint256 _doubleSigningJailUntilBlock) internal { - doubleSigningJailUntilBlock = _doubleSigningJailUntilBlock; - emit DoubleSigningJailUntilBlockUpdated(_doubleSigningJailUntilBlock); - } - - /** - * @dev Sets the threshold to slash when trusted organization does not vote for bridge operators. - */ - function _setBridgeVotingThreshold(uint256 _threshold) internal { - bridgeVotingThreshold = _threshold; - emit BridgeVotingThresholdUpdated(_threshold); - } - - /** - * @dev Sets the amount of RON to slash bridge voting. - */ - function _setBridgeVotingSlashAmount(uint256 _amount) internal { - bridgeVotingSlashAmount = _amount; - emit BridgeVotingSlashAmountUpdated(_amount); - } - - /** - * @dev Sanity check the address to be slashed - */ - function _shouldSlash(address _addr) internal view returns (bool) { - return - (msg.sender != _addr) && - _validatorContract.isBlockProducer(_addr) && - !_maintenanceContract.maintaining(_addr, block.number); - } -} diff --git a/contracts/ronin/slash-indicator/SlashBridgeOperator.sol b/contracts/ronin/slash-indicator/SlashBridgeOperator.sol new file mode 100644 index 000000000..04c4a190f --- /dev/null +++ b/contracts/ronin/slash-indicator/SlashBridgeOperator.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../../extensions/consumers/PercentageConsumer.sol"; +import "../../extensions/collections/HasProxyAdmin.sol"; +import "../../interfaces/ISlashBridgeOperator.sol"; + +contract SlashBridgeOperator is ISlashBridgeOperator, HasProxyAdmin, PercentageConsumer { + /** + * @dev The bridge operators will be deprecated reward if (s)he missed more than the ratio. + * Values 0-10,000 map to 0%-100%. + */ + uint256 internal _missingVotesRatioTier1; + /** + * @dev The bridge operators will be deprecated all rewards including bridge reward and mining reward if (s)he missed + * more than the ratio. Values 0-10,000 map to 0%-100%. + */ + uint256 internal _missingVotesRatioTier2; + /// @dev The number of blocks to jail the corresponding block producer when its bridge operator is slashed tier-2. + uint256 internal _jailDurationForMissingVotesRatioTier2; + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + */ + uint256[50] private ______gap; + + /** + * @inheritdoc ISlashBridgeOperator + */ + function getBridgeOperatorSlashingConfigs() + external + view + override + returns ( + uint256, + uint256, + uint256 + ) + { + return (_missingVotesRatioTier1, _missingVotesRatioTier2, _jailDurationForMissingVotesRatioTier2); + } + + /** + * @inheritdoc ISlashBridgeOperator + */ + function setBridgeOperatorSlashingConfigs( + uint256 _ratioTier1, + uint256 _ratioTier2, + uint256 _jailDurationTier2 + ) external override onlyAdmin { + _setBridgeOperatorSlashingConfigs(_ratioTier1, _ratioTier2, _jailDurationTier2); + } + + /** + * @dev See `ISlashBridgeOperator-setBridgeOperatorSlashingConfigs`. + */ + function _setBridgeOperatorSlashingConfigs( + uint256 _ratioTier1, + uint256 _ratioTier2, + uint256 _jailDurationTier2 + ) internal { + require( + _ratioTier1 <= _ratioTier2 && _ratioTier1 <= _MAX_PERCENTAGE && _ratioTier2 <= _MAX_PERCENTAGE, + "SlashIndicator: invalid ratios" + ); + _missingVotesRatioTier1 = _ratioTier1; + _missingVotesRatioTier2 = _ratioTier2; + _jailDurationForMissingVotesRatioTier2 = _jailDurationTier2; + emit BridgeOperatorSlashingConfigsUpdated(_ratioTier1, _ratioTier2, _jailDurationTier2); + } +} diff --git a/contracts/ronin/slash-indicator/SlashBridgeVoting.sol b/contracts/ronin/slash-indicator/SlashBridgeVoting.sol new file mode 100644 index 000000000..79e1198be --- /dev/null +++ b/contracts/ronin/slash-indicator/SlashBridgeVoting.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../../libraries/Math.sol"; +import "../../interfaces/ISlashBridgeVoting.sol"; +import "../../extensions/collections/HasRoninTrustedOrganizationContract.sol"; +import "../../extensions/collections/HasRoninGovernanceAdminContract.sol"; +import "../../extensions/collections/HasValidatorContract.sol"; + +contract SlashBridgeVoting is + ISlashBridgeVoting, + HasValidatorContract, + HasRoninTrustedOrganizationContract, + HasRoninGovernanceAdminContract +{ + /// @dev Mapping from validator address => period index => bridge voting slashed + mapping(address => mapping(uint256 => bool)) internal _bridgeVotingSlashed; + /// @dev The threshold to slash when a trusted organization does not vote for bridge operators. + uint256 internal _bridgeVotingThreshold; + /// @dev The amount of RON to slash bridge voting. + uint256 internal _bridgeVotingSlashAmount; + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + */ + uint256[50] private ______gap; + + /** + * @inheritdoc ISlashBridgeVoting + */ + function slashBridgeVoting(address _consensusAddr) external { + IRoninTrustedOrganization.TrustedOrganization memory _org = _roninTrustedOrganizationContract + .getTrustedOrganization(_consensusAddr); + uint256 _lastVotedBlock = Math.max(_roninGovernanceAdminContract.lastVotedBlock(_org.bridgeVoter), _org.addedBlock); + uint256 _period = _validatorContract.currentPeriod(); + if (block.number - _lastVotedBlock > _bridgeVotingThreshold && !_bridgeVotingSlashed[_consensusAddr][_period]) { + _bridgeVotingSlashed[_consensusAddr][_period] = true; + emit Slashed(_consensusAddr, SlashType.BRIDGE_VOTING, _period); + _validatorContract.slash(_consensusAddr, 0, _bridgeVotingSlashAmount); + } + } + + /** + * @inheritdoc ISlashBridgeVoting + */ + function getBridgeVotingSlashingConfigs() external view override returns (uint256, uint256) { + return (_bridgeVotingThreshold, _bridgeVotingSlashAmount); + } + + /** + * @inheritdoc ISlashBridgeVoting + */ + function setBridgeVotingSlashingConfigs(uint256 _threshold, uint256 _slashAmount) external override onlyAdmin { + _setBridgeVotingSlashingConfigs(_threshold, _slashAmount); + } + + /** + * @dev See `ISlashBridgeVoting-setBridgeVotingSlashingConfigs`. + */ + function _setBridgeVotingSlashingConfigs(uint256 _threshold, uint256 _slashAmount) internal { + _bridgeVotingThreshold = _threshold; + _slashAmount = _bridgeVotingSlashAmount; + emit BridgeVotingSlashingConfigsUpdated(_threshold, _slashAmount); + } +} diff --git a/contracts/ronin/slash-indicator/SlashDoubleSign.sol b/contracts/ronin/slash-indicator/SlashDoubleSign.sol new file mode 100644 index 000000000..cf523834b --- /dev/null +++ b/contracts/ronin/slash-indicator/SlashDoubleSign.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../../interfaces/ISlashDoubleSign.sol"; +import "../../precompile-usages/PrecompileUsageValidateDoubleSign.sol"; +import "../../extensions/collections/HasValidatorContract.sol"; + +abstract contract SlashDoubleSign is ISlashDoubleSign, HasValidatorContract, PrecompileUsageValidateDoubleSign { + /// @dev The amount of RON to slash double sign. + uint256 internal _slashDoubleSignAmount; + /// @dev The block number that the punished validator will be jailed until, due to double signing. + uint256 internal _doubleSigningJailUntilBlock; + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + */ + uint256[50] private ______gap; + + /** + * @inheritdoc ISlashDoubleSign + */ + function slashDoubleSign( + address _consensuAddr, + bytes calldata _header1, + bytes calldata _header2 + ) external override { + require(msg.sender == block.coinbase, "SlashIndicator: method caller must be coinbase"); + if (!_shouldSlash(_consensuAddr)) { + return; + } + + if (_pcValidateEvidence(_header1, _header2)) { + uint256 _period = _validatorContract.currentPeriod(); + emit Slashed(_consensuAddr, SlashType.DOUBLE_SIGNING, _period); + _validatorContract.slash(_consensuAddr, _doubleSigningJailUntilBlock, _slashDoubleSignAmount); + } + } + + /** + * @inheritdoc ISlashDoubleSign + */ + function getDoubleSignSlashingConfigs() external view override returns (uint256, uint256) { + return (_slashDoubleSignAmount, _doubleSigningJailUntilBlock); + } + + /** + * @inheritdoc ISlashDoubleSign + */ + function setDoubleSignSlashingConfigs(uint256 _slashAmount, uint256 _jailUntilBlock) external override onlyAdmin { + _setDoubleSignSlashingConfigs(_slashAmount, _jailUntilBlock); + } + + /** + * @dev See `ISlashDoubleSign-setDoubleSignSlashingConfigs`. + */ + function _setDoubleSignSlashingConfigs(uint256 _slashAmount, uint256 _jailUntilBlock) internal { + _slashDoubleSignAmount = _slashAmount; + _doubleSigningJailUntilBlock = _jailUntilBlock; + emit DoubleSignSlashingConfigsUpdated(_slashAmount, _jailUntilBlock); + } + + /** + * @dev Returns whether the account `_addr` should be slashed or not. + */ + function _shouldSlash(address _addr) internal view virtual returns (bool); +} diff --git a/contracts/ronin/slash-indicator/SlashIndicator.sol b/contracts/ronin/slash-indicator/SlashIndicator.sol new file mode 100644 index 000000000..e924f725e --- /dev/null +++ b/contracts/ronin/slash-indicator/SlashIndicator.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "../../interfaces/ISlashIndicator.sol"; +import "../../extensions/collections/HasMaintenanceContract.sol"; +import "./SlashDoubleSign.sol"; +import "./SlashBridgeVoting.sol"; +import "./SlashBridgeOperator.sol"; +import "./SlashUnavailability.sol"; + +contract SlashIndicator is + ISlashIndicator, + SlashDoubleSign, + SlashBridgeVoting, + SlashBridgeOperator, + SlashUnavailability, + HasMaintenanceContract, + Initializable +{ + constructor() { + _disableInitializers(); + } + + /** + * @dev Initializes the contract storage. + */ + function initialize( + address __validatorContract, + address __maintenanceContract, + address __roninTrustedOrganizationContract, + address __roninGovernanceAdminContract, + // _bridgeOperatorSlashingConfigs[0]: _missingVotesRatioTier1 + // _bridgeOperatorSlashingConfigs[1]: _missingVotesRatioTier2 + // _bridgeOperatorSlashingConfigs[2]: _jailDurationForMissingVotesRatioTier2 + uint256[3] calldata _bridgeOperatorSlashingConfigs, + // _bridgeVotingSlashingConfigs[0]: _bridgeVotingThreshold + // _bridgeVotingSlashingConfigs[1]: _bridgeVotingSlashAmount + uint256[2] calldata _bridgeVotingSlashingConfigs, + // _doubleSignSlashingConfigs[0]: _slashDoubleSignAmount + // _doubleSignSlashingConfigs[1]: _doubleSigningJailUntilBlock + uint256[2] calldata _doubleSignSlashingConfigs, + // _unavailabilitySlashingConfigs[0]: _unavailabilityTier1Threshold + // _unavailabilitySlashingConfigs[1]: _unavailabilityTier2Threshold + // _unavailabilitySlashingConfigs[2]: _slashAmountForUnavailabilityTier2Threshold + // _unavailabilitySlashingConfigs[3]: _jailDurationForUnavailabilityTier2Threshold + uint256[4] calldata _unavailabilitySlashingConfigs + ) external initializer { + _setValidatorContract(__validatorContract); + _setMaintenanceContract(__maintenanceContract); + _setRoninTrustedOrganizationContract(__roninTrustedOrganizationContract); + _setRoninGovernanceAdminContract(__roninGovernanceAdminContract); + _setBridgeOperatorSlashingConfigs( + _bridgeOperatorSlashingConfigs[0], + _bridgeOperatorSlashingConfigs[1], + _bridgeOperatorSlashingConfigs[2] + ); + _setBridgeVotingSlashingConfigs(_bridgeVotingSlashingConfigs[0], _bridgeVotingSlashingConfigs[1]); + _setDoubleSignSlashingConfigs(_doubleSignSlashingConfigs[0], _doubleSignSlashingConfigs[1]); + _setUnavailabilitySlashingConfigs( + _unavailabilitySlashingConfigs[0], + _unavailabilitySlashingConfigs[1], + _unavailabilitySlashingConfigs[2], + _unavailabilitySlashingConfigs[3] + ); + } + + /** + * @dev Sanity check the address to be slashed + */ + function _shouldSlash(address _addr) internal view override(SlashDoubleSign, SlashUnavailability) returns (bool) { + return + (msg.sender != _addr) && + _validatorContract.isBlockProducer(_addr) && + !_maintenanceContract.maintaining(_addr, block.number); + } +} diff --git a/contracts/ronin/slash-indicator/SlashUnavailability.sol b/contracts/ronin/slash-indicator/SlashUnavailability.sol new file mode 100644 index 000000000..7f20ab15e --- /dev/null +++ b/contracts/ronin/slash-indicator/SlashUnavailability.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../../interfaces/ISlashUnavailability.sol"; +import "../../extensions/collections/HasValidatorContract.sol"; + +abstract contract SlashUnavailability is ISlashUnavailability, HasValidatorContract { + /// @dev The last block that a validator is slashed for unavailability. + uint256 public lastUnavailabilitySlashedBlock; + /// @dev Mapping from validator address => period index => unavailability indicator. + mapping(address => mapping(uint256 => uint256)) internal _unavailabilityIndicator; + + /// @dev The mining reward will be deprecated, if (s)he missed more than this threshold. + uint256 internal _unavailabilityTier1Threshold; + /** + * @dev The mining reward will be deprecated, (s)he will be put in jailed, and will be deducted + * self-staking if (s)he misses more than this threshold. + */ + uint256 internal _unavailabilityTier2Threshold; + /// @dev The amount of RON to deduct from self-staking of a block producer when (s)he is slashed tier-2. + uint256 internal _slashAmountForUnavailabilityTier2Threshold; + /// @dev The number of blocks to jail a block producer when (s)he is slashed tier-2. + uint256 internal _jailDurationForUnavailabilityTier2Threshold; + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + */ + uint256[50] private ______gap; + + modifier oncePerBlock() { + require( + block.number > lastUnavailabilitySlashedBlock, + "SlashIndicator: cannot slash a validator twice or slash more than one validator in one block" + ); + lastUnavailabilitySlashedBlock = block.number; + _; + } + + /** + * @inheritdoc ISlashUnavailability + */ + function slashUnavailability(address _validatorAddr) external override oncePerBlock { + require(msg.sender == block.coinbase, "SlashUnavailability: method caller must be coinbase"); + if (!_shouldSlash(_validatorAddr)) { + return; + } + + uint256 _period = _validatorContract.currentPeriod(); + uint256 _count = ++_unavailabilityIndicator[_validatorAddr][_period]; + + if (_count == _unavailabilityTier2Threshold) { + emit Slashed(_validatorAddr, SlashType.UNAVAILABILITY_TIER_2, _period); + _validatorContract.slash( + _validatorAddr, + block.number + _jailDurationForUnavailabilityTier2Threshold, + _slashAmountForUnavailabilityTier2Threshold + ); + } else if (_count == _unavailabilityTier1Threshold) { + emit Slashed(_validatorAddr, SlashType.UNAVAILABILITY_TIER_1, _period); + _validatorContract.slash(_validatorAddr, 0, 0); + } + } + + /** + * @inheritdoc ISlashUnavailability + */ + function setUnavailabilitySlashingConfigs( + uint256 _tier1Threshold, + uint256 _tier2Threshold, + uint256 _slashAmountForTier2Threshold, + uint256 _jailDurationForTier2Threshold + ) external override onlyAdmin { + _setUnavailabilitySlashingConfigs( + _tier1Threshold, + _tier2Threshold, + _slashAmountForTier2Threshold, + _jailDurationForTier2Threshold + ); + } + + /** + * @inheritdoc ISlashUnavailability + */ + function getUnavailabilitySlashingConfigs() + external + view + override + returns ( + uint256, + uint256, + uint256, + uint256 + ) + { + return ( + _unavailabilityTier1Threshold, + _unavailabilityTier2Threshold, + _slashAmountForUnavailabilityTier2Threshold, + _jailDurationForUnavailabilityTier2Threshold + ); + } + + /** + * @inheritdoc ISlashUnavailability + */ + function currentUnavailabilityIndicator(address _validator) external view override returns (uint256) { + return getUnavailabilityIndicator(_validator, _validatorContract.currentPeriod()); + } + + /** + * @inheritdoc ISlashUnavailability + */ + function getUnavailabilityIndicator(address _validator, uint256 _period) public view override returns (uint256) { + return _unavailabilityIndicator[_validator][_period]; + } + + /** + * @dev See `ISlashUnavailability-setUnavailabilitySlashingConfigs`. + */ + function _setUnavailabilitySlashingConfigs( + uint256 _tier1Threshold, + uint256 _tier2Threshold, + uint256 _slashAmountForTier2Threshold, + uint256 _jailDurationForTier2Threshold + ) internal { + require(_unavailabilityTier1Threshold <= _unavailabilityTier2Threshold, "SlashUnavailability: invalid threshold"); + _unavailabilityTier1Threshold = _tier1Threshold; + _unavailabilityTier2Threshold = _tier2Threshold; + _slashAmountForUnavailabilityTier2Threshold = _slashAmountForTier2Threshold; + _jailDurationForUnavailabilityTier2Threshold = _jailDurationForTier2Threshold; + emit UnavailabilitySlashingConfigsUpdated( + _tier1Threshold, + _tier2Threshold, + _slashAmountForTier2Threshold, + _jailDurationForTier2Threshold + ); + } + + /** + * @dev Returns whether the account `_addr` should be slashed or not. + */ + function _shouldSlash(address _addr) internal view virtual returns (bool); +} diff --git a/contracts/ronin/validator/CandidateManager.sol b/contracts/ronin/validator/CandidateManager.sol index 3404de87d..5214983ef 100644 --- a/contracts/ronin/validator/CandidateManager.sol +++ b/contracts/ronin/validator/CandidateManager.sol @@ -17,6 +17,12 @@ abstract contract CandidateManager is ICandidateManager, HasStakingContract { /// @dev Mapping from candidate address => their info mapping(address => ValidatorCandidate) internal _candidateInfo; + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + */ + uint256[50] private ______gap; + /** * @inheritdoc ICandidateManager */ diff --git a/contracts/ronin/validator/RoninValidatorSet.sol b/contracts/ronin/validator/RoninValidatorSet.sol index 2c60249dc..a77a1a7bb 100644 --- a/contracts/ronin/validator/RoninValidatorSet.sol +++ b/contracts/ronin/validator/RoninValidatorSet.sol @@ -3,13 +3,14 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "../../extensions/RONTransferHelper.sol"; import "../../extensions/collections/HasStakingVestingContract.sol"; import "../../extensions/collections/HasStakingContract.sol"; import "../../extensions/collections/HasSlashIndicatorContract.sol"; import "../../extensions/collections/HasMaintenanceContract.sol"; import "../../extensions/collections/HasRoninTrustedOrganizationContract.sol"; +import "../../extensions/collections/HasBridgeTrackingContract.sol"; +import "../../extensions/consumers/PercentageConsumer.sol"; import "../../interfaces/IRoninValidatorSet.sol"; import "../../libraries/Math.sol"; import "../../libraries/EnumFlags.sol"; @@ -27,14 +28,13 @@ contract RoninValidatorSet is HasSlashIndicatorContract, HasMaintenanceContract, HasRoninTrustedOrganizationContract, + HasBridgeTrackingContract, CandidateManager, + PercentageConsumer, Initializable { using EnumFlags for EnumFlags.ValidatorFlag; - /// @dev The total seconds in a day - uint256 internal constant _SECS_IN_DAY = 86400; - /// @dev The maximum number of validator. uint256 internal _maxValidatorNumber; /// @dev The number of blocks in a epoch @@ -53,16 +53,21 @@ contract RoninValidatorSet is /// @dev The number of slot that is reserved for prioritized validators uint256 internal _maxPrioritizedValidatorNumber; - /// @dev Mapping from validator address => the last period that the validator has no pending reward - mapping(address => mapping(uint256 => bool)) internal _rewardDeprecatedAtPeriod; - /// @dev Mapping from validator address => the last block that the validator is jailed + /// @dev Mapping from consensus address => the last period that the block producer has no pending reward + mapping(address => mapping(uint256 => bool)) internal _miningRewardDeprecatedAtPeriod; + /// @dev Mapping from consensus address => the last period that the block operator has no pending reward + mapping(address => mapping(uint256 => bool)) internal _bridgeRewardDeprecatedAtPeriod; + /// @dev Mapping from consensus address => the last block that the validator is jailed mapping(address => uint256) internal _jailedUntil; - /// @dev Mapping from validator address => pending reward from producing block + /// @dev Mapping from consensus address => pending reward from producing block mapping(address => uint256) internal _miningReward; - /// @dev Mapping from validator address => pending reward from delegating + /// @dev Mapping from consensus address => pending reward from delegating mapping(address => uint256) internal _delegatingReward; - /// @dev Mapping from validator address => pending reward for being bridge operator + + /// @dev The total reward for bridge operators + uint256 internal _totalBridgeReward; + /// @dev Mapping from consensus address => pending reward for being bridge operator mapping(address => uint256) internal _bridgeOperatingReward; modifier onlyCoinbase() { @@ -75,6 +80,15 @@ contract RoninValidatorSet is _; } + modifier oncePerEpoch() { + require( + epochOf(_lastUpdatedBlock) < epochOf(block.number), + "RoninValidatorSet: query for already wrapped up epoch" + ); + _lastUpdatedBlock = block.number; + _; + } + constructor() { _disableInitializers(); } @@ -96,6 +110,7 @@ contract RoninValidatorSet is address __stakingVestingContract, address __maintenanceContract, address __roninTrustedOrganizationContract, + address __bridgeTrackingContract, uint256 __maxValidatorNumber, uint256 __maxValidatorCandidate, uint256 __maxPrioritizedValidatorNumber, @@ -105,6 +120,7 @@ contract RoninValidatorSet is _setStakingContract(__stakingContract); _setStakingVestingContract(__stakingVestingContract); _setMaintenanceContract(__maintenanceContract); + _setBridgeTrackingContract(__bridgeTrackingContract); _setRoninTrustedOrganizationContract(__roninTrustedOrganizationContract); _setMaxValidatorNumber(__maxValidatorNumber); _setMaxValidatorCandidate(__maxValidatorCandidate); @@ -121,46 +137,40 @@ contract RoninValidatorSet is */ function submitBlockReward() external payable override onlyCoinbase { uint256 _submittedReward = msg.value; - if (_submittedReward == 0) { - return; - } - address _coinbaseAddr = msg.sender; + + uint256 _bridgeOperatorBonus = _stakingVestingContract.requestBridgeOperatorBonus(); + _totalBridgeReward += _bridgeOperatorBonus; + // Deprecates reward for non-validator or slashed validator if ( - !isBlockProducer(_coinbaseAddr) || _jailed(_coinbaseAddr) || _rewardDeprecated(_coinbaseAddr, currentPeriod()) + !isBlockProducer(_coinbaseAddr) || + _jailed(_coinbaseAddr) || + _miningRewardDeprecated(_coinbaseAddr, currentPeriod()) ) { - emit RewardDeprecated(_coinbaseAddr, _submittedReward); + emit BlockRewardRewardDeprecated(_coinbaseAddr, _submittedReward); return; } - (uint256 _validatorStakingVesting, uint256 _bridgeValidatorStakingVesting) = _stakingVestingContract.requestBonus(); - uint256 _reward = _submittedReward + _validatorStakingVesting; - + uint256 _blockProducerBonus = _stakingVestingContract.requestValidatorBonus(); + uint256 _reward = _submittedReward + _blockProducerBonus; uint256 _rate = _candidateInfo[_coinbaseAddr].commissionRate; - uint256 _miningAmount = (_rate * _reward) / 100_00; - uint256 _delegatingAmount = _reward - _miningAmount; + uint256 _miningAmount = (_rate * _reward) / 100_00; _miningReward[_coinbaseAddr] += _miningAmount; + + uint256 _delegatingAmount = _reward - _miningAmount; _delegatingReward[_coinbaseAddr] += _delegatingAmount; - _bridgeOperatingReward[_coinbaseAddr] += _bridgeValidatorStakingVesting; _stakingContract.recordReward(_coinbaseAddr, _delegatingAmount); - emit BlockRewardSubmitted(_coinbaseAddr, _submittedReward, _validatorStakingVesting); + emit BlockRewardSubmitted(_coinbaseAddr, _submittedReward, _blockProducerBonus); } /** * @inheritdoc IRoninValidatorSet */ - function wrapUpEpoch() external payable virtual override onlyCoinbase whenEpochEnding { - require( - epochOf(_lastUpdatedBlock) < epochOf(block.number), - "RoninValidatorSet: query for already wrapped up epoch" - ); - _lastUpdatedBlock = block.number; - + function wrapUpEpoch() external payable virtual override onlyCoinbase whenEpochEnding oncePerEpoch { uint256 _newPeriod = _computePeriod(block.timestamp); bool _periodEnding = _isPeriodEnding(_newPeriod); - _lastUpdatedPeriod = _newPeriod; address[] memory _currentValidators = getValidators(); uint256 _epoch = epochOf(block.number); @@ -168,18 +178,16 @@ contract RoninValidatorSet is if (_periodEnding) { uint256 _totalDelegatingReward = _distributeRewardToTreasuriesAndCalculateTotalDelegatingReward( - _currentValidators, - _period + _period, + _currentValidators ); - _settleAndTransferDelegatingRewards(_currentValidators, _totalDelegatingReward); - _currentValidators = _syncValidatorSet(); } _revampBlockProducers(_currentValidators); - emit WrappedUpEpoch(_period, _epoch, _periodEnding); + _lastUpdatedPeriod = _newPeriod; } /////////////////////////////////////////////////////////////////////////////////////// @@ -194,7 +202,8 @@ contract RoninValidatorSet is uint256 _newJailedUntil, uint256 _slashAmount ) external onlySlashIndicatorContract { - _rewardDeprecatedAtPeriod[_validatorAddr][currentPeriod()] = true; + uint256 _period = currentPeriod(); + _miningRewardDeprecatedAtPeriod[_validatorAddr][_period] = true; delete _miningReward[_validatorAddr]; delete _delegatingReward[_validatorAddr]; IStaking(_stakingContract).sinkPendingReward(_validatorAddr); @@ -207,7 +216,7 @@ contract RoninValidatorSet is IStaking(_stakingContract).deductStakedAmount(_validatorAddr, _slashAmount); } - emit ValidatorPunished(_validatorAddr, _jailedUntil[_validatorAddr], _slashAmount); + emit ValidatorPunished(_validatorAddr, _period, _jailedUntil[_validatorAddr], _slashAmount, true, false); } /** @@ -223,26 +232,31 @@ contract RoninValidatorSet is /** * @inheritdoc IRoninValidatorSet */ - function rewardDeprecated(address[] memory _addrList) external view override returns (bool[] memory _result) { - _result = new bool[](_addrList.length); + function miningRewardDeprecated(address[] memory _blockProducers) + external + view + override + returns (bool[] memory _result) + { + _result = new bool[](_blockProducers.length); uint256 _period = currentPeriod(); - for (uint256 _i; _i < _addrList.length; _i++) { - _result[_i] = _rewardDeprecated(_addrList[_i], _period); + for (uint256 _i; _i < _blockProducers.length; _i++) { + _result[_i] = _miningRewardDeprecated(_blockProducers[_i], _period); } } /** * @inheritdoc IRoninValidatorSet */ - function rewardDeprecatedAtPeriod(address[] memory _addrList, uint256 _period) + function miningRewardDeprecatedAtPeriod(address[] memory _blockProducers, uint256 _period) external view override returns (bool[] memory _result) { - _result = new bool[](_addrList.length); - for (uint256 _i; _i < _addrList.length; _i++) { - _result[_i] = _rewardDeprecated(_addrList[_i], _period); + _result = new bool[](_blockProducers.length); + for (uint256 _i; _i < _blockProducers.length; _i++) { + _result[_i] = _miningRewardDeprecated(_blockProducers[_i], _period); } } @@ -345,15 +359,6 @@ contract RoninValidatorSet is } } - /** - * Notice: A validator is always a bride operator - * - * @inheritdoc IRoninValidatorSet - */ - function totalBridgeOperators() external view returns (uint256) { - return validatorCount; - } - /** * @inheritdoc IRoninValidatorSet */ @@ -394,6 +399,15 @@ contract RoninValidatorSet is return _maxPrioritizedValidatorNumber; } + /** + * Notice: A validator is always a bride operator + * + * @inheritdoc IRoninValidatorSet + */ + function totalBridgeOperators() public view returns (uint256) { + return validatorCount; + } + /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR ADMIN // /////////////////////////////////////////////////////////////////////////////////////// @@ -425,51 +439,137 @@ contract RoninValidatorSet is * */ function _distributeRewardToTreasuriesAndCalculateTotalDelegatingReward( - address[] memory _currentValidators, - uint256 _period + uint256 _period, + address[] memory _currentValidators ) private returns (uint256 _totalDelegatingReward) { + address _consensusAddr; + address payable _treasury; + IBridgeTracking _bridgeTracking = _bridgeTrackingContract; + + uint256 _totalBridgeBallots = _bridgeTracking.totalBallots(_period); + uint256 _totalBridgeVotes = _bridgeTracking.totalVotes(_period); + uint256[] memory _bridgeBallots = _bridgeTracking.bulkTotalBallotsOf(_period, _currentValidators); + ( + uint256 _missingVotesRatioTier1, + uint256 _missingVotesRatioTier2, + uint256 _jailDurationForMissingVotesRatioTier2 + ) = _slashIndicatorContract.getBridgeOperatorSlashingConfigs(); + for (uint _i = 0; _i < _currentValidators.length; _i++) { - address _validatorAddr = _currentValidators[_i]; + _consensusAddr = _currentValidators[_i]; + _treasury = _candidateInfo[_consensusAddr].treasuryAddr; + _updateValidatorReward( + _period, + _consensusAddr, + _bridgeBallots[_i], + _totalBridgeVotes, + _totalBridgeBallots, + _missingVotesRatioTier1, + _missingVotesRatioTier2, + _jailDurationForMissingVotesRatioTier2 + ); - if (_jailed(_validatorAddr) || _rewardDeprecated(_validatorAddr, _period)) { - continue; + if (!_bridgeRewardDeprecated(_consensusAddr, _period)) { + _distributeBridgeOperatingReward(_consensusAddr, _candidateInfo[_consensusAddr].bridgeOperatorAddr, _treasury); } - _totalDelegatingReward += _delegatingReward[_validatorAddr]; - delete _delegatingReward[_validatorAddr]; + if (!_jailed(_consensusAddr) && !_miningRewardDeprecated(_consensusAddr, _period)) { + _totalDelegatingReward += _delegatingReward[_consensusAddr]; + _distributeMiningReward(_consensusAddr, _treasury); + } + + delete _delegatingReward[_consensusAddr]; + delete _miningReward[_consensusAddr]; + delete _bridgeOperatingReward[_consensusAddr]; + } + delete _totalBridgeReward; + } + + /** + * @dev Updates validator reward based on the corresponding bridge operator performance. + */ + function _updateValidatorReward( + uint256 _period, + address _validator, + uint256 _validatorBallots, + uint256 _totalVotes, + uint256 _totalBallots, + uint256 _ratioTier1, + uint256 _ratioTier2, + uint256 _jailDurationTier2 + ) internal { + // Shares equally in case the bridge has nothing to votes + if (_totalBallots == 0 && _totalVotes == 0) { + uint256 _shareRatio = _MAX_PERCENTAGE / totalBridgeOperators(); + _bridgeOperatingReward[_validator] = (_shareRatio * _totalBridgeReward) / _MAX_PERCENTAGE; + return; + } - _distributeRewardToTreasury(_validatorAddr); + uint256 _votedRatio = (_validatorBallots * _MAX_PERCENTAGE) / _totalVotes; + uint256 _missedRatio = _MAX_PERCENTAGE - _votedRatio; + if (_missedRatio > _ratioTier2) { + _bridgeRewardDeprecatedAtPeriod[_validator][_period] = true; + _miningRewardDeprecatedAtPeriod[_validator][_period] = true; + _jailedUntil[_validator] = Math.max(block.number + _jailDurationTier2, _jailedUntil[_validator]); + emit ValidatorPunished(_validator, _period, _jailedUntil[_validator], 0, true, true); + } else if (_missedRatio > _ratioTier1) { + _bridgeRewardDeprecatedAtPeriod[_validator][_period] = true; + emit ValidatorPunished(_validator, _period, _jailedUntil[_validator], 0, false, true); + } else if (_totalBallots > 0) { + uint256 _shareRatio = (_validatorBallots * _MAX_PERCENTAGE) / _totalBallots; + _bridgeOperatingReward[_validator] = (_shareRatio * _totalBridgeReward) / _MAX_PERCENTAGE; } } /** - * @dev Distribute bonus of staking vesting and mining fee for block producer; and bonus of staking vesting for - * bridge oparators to the treasury address of a validator. + * @dev Distributes bonus of staking vesting and mining fee for the block producer. * - * Emits the `MiningRewardDistributed` event once the validator has an amount of mining reward. - * Emits the `BridgeOperatorRewardDistributed` once the validator has an amount of bridge reward. + * Emits the `MiningRewardDistributed` once the reward is distributed successfully. + * Emits the `MiningRewardDistributionFailed` once the contract fails to distribute reward. * * Note: This method should be called once in the end of each period. * */ - function _distributeRewardToTreasury(address _validatorAddr) private { - address payable _treasury = _candidateInfo[_validatorAddr].treasuryAddr; + function _distributeMiningReward(address _consensusAddr, address payable _treasury) private { + uint256 _amount = _miningReward[_consensusAddr]; + if (_amount > 0) { + if (_sendRON(_treasury, _amount)) { + emit MiningRewardDistributed(_consensusAddr, _treasury, _amount); + return; + } - uint256 _miningAmount = _miningReward[_validatorAddr]; - if (_miningAmount > 0) { - delete _miningReward[_validatorAddr]; - require(_sendRON(_treasury, _miningAmount), "RoninValidatorSet: could not transfer RON treasury address"); - emit MiningRewardDistributed(_validatorAddr, _treasury, _miningAmount); + emit MiningRewardDistributionFailed(_consensusAddr, _treasury, _amount, address(this).balance); } + } - uint256 _bridgeOperatingAmount = _bridgeOperatingReward[_validatorAddr]; - if (_bridgeOperatingAmount > 0) { - delete _bridgeOperatingReward[_validatorAddr]; - require( - _sendRON(_treasury, _bridgeOperatingAmount), - "RoninValidatorSet: could not transfer RON to bridge operator address" + /** + * @dev Distribute bonus of staking vesting for the bridge operator. + * + * Emits the `BridgeOperatorRewardDistributed` once the reward is distributed successfully. + * Emits the `BridgeOperatorRewardDistributionFailed` once the contract fails to distribute reward. + * + * Note: This method should be called once in the end of each period. + * + */ + function _distributeBridgeOperatingReward( + address _consensusAddr, + address _bridgeOperator, + address payable _treasury + ) private { + uint256 _amount = _bridgeOperatingReward[_consensusAddr]; + if (_amount > 0) { + if (_sendRON(_treasury, _amount)) { + emit BridgeOperatorRewardDistributed(_consensusAddr, _bridgeOperator, _treasury, _amount); + return; + } + + emit BridgeOperatorRewardDistributionFailed( + _consensusAddr, + _bridgeOperator, + _treasury, + _amount, + address(this).balance ); - emit BridgeOperatorRewardDistributed(_validatorAddr, _treasury, _bridgeOperatingAmount); } } @@ -477,7 +577,8 @@ contract RoninValidatorSet is * @dev Helper function to settle rewards for delegators of `_currentValidators` at the end of each period, * then transfer the rewards from this contract to the staking contract, in order to finalize a period. * - * Emit `StakingRewardDistributed` event. + * Emits the `StakingRewardDistributed` once the reward is distributed successfully. + * Emits the `StakingRewardDistributionFailed` once the contract fails to distribute reward. * * Note: This method should be called once in the end of each period. * @@ -486,14 +587,15 @@ contract RoninValidatorSet is private { IStaking _staking = _stakingContract; - _staking.settleRewardPools(_currentValidators); + if (_totalDelegatingReward > 0) { - require( - _sendRON(payable(address(_staking)), _totalDelegatingReward), - "RoninValidatorSet: could not transfer RON to staking contract" - ); - emit StakingRewardDistributed(_totalDelegatingReward); + if (_sendRON(payable(address(_staking)), _totalDelegatingReward)) { + emit StakingRewardDistributed(_totalDelegatingReward); + return; + } + + emit StakingRewardDistributionFailed(_totalDelegatingReward, address(this).balance); } } @@ -603,10 +705,17 @@ contract RoninValidatorSet is } /** - * @dev Returns whether the validator has no pending reward in that period. + * @dev Returns whether the block producer has no pending reward in that period. + */ + function _miningRewardDeprecated(address _validatorAddr, uint256 _period) internal view returns (bool) { + return _miningRewardDeprecatedAtPeriod[_validatorAddr][_period]; + } + + /** + * @dev Returns whether the bridge operator has no pending reward in the period. */ - function _rewardDeprecated(address _validatorAddr, uint256 _period) internal view returns (bool) { - return _rewardDeprecatedAtPeriod[_validatorAddr][_period]; + function _bridgeRewardDeprecated(address _validatorAddr, uint256 _period) internal view returns (bool) { + return _bridgeRewardDeprecatedAtPeriod[_validatorAddr][_period]; } /** @@ -655,7 +764,7 @@ contract RoninValidatorSet is * @dev Returns the calculated period. */ function _computePeriod(uint256 _timestamp) internal pure returns (uint256) { - return _timestamp / _SECS_IN_DAY; + return _timestamp / 1 days; } /** diff --git a/src/config.ts b/src/config.ts index 5a14532cc..ced60ce7d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,18 +1,17 @@ -import { BigNumber, BigNumberish } from 'ethers'; +import { BigNumber } from 'ethers'; import { ethers } from 'hardhat'; -import { Address } from 'hardhat-deploy/dist/types'; -import { TrustedOrganizationStruct } from './types/IRoninTrustedOrganization'; - -export enum Network { - Hardhat = 'hardhat', - Devnet = 'ronin-devnet', - Testnet = 'ronin-testnet', - Mainnet = 'ronin-mainnet', - Goerli = 'goerli', - Ethereum = 'ethereum', -} - -export type LiteralNetwork = Network | string; +import { + GeneralConfig, + LiteralNetwork, + MainchainGovernanceAdminConfig, + MaintenanceConfig, + Network, + RoninTrustedOrganizationConfig, + RoninValidatorSetConfig, + SlashIndicatorConfig, + StakingConfig, + StakingVestingConfig, +} from './utils'; export const commonNetworks: LiteralNetwork[] = [Network.Hardhat, Network.Devnet]; export const mainchainNetworks: LiteralNetwork[] = [...commonNetworks, Network.Goerli, Network.Ethereum]; @@ -25,107 +24,27 @@ export const allNetworks: LiteralNetwork[] = [ export const defaultAddress = '0x0000000000000000000000000000000000000000'; -export interface AddressExtended { - address: Address; - nonce?: number; -} - -export interface InitAddr { - [network: LiteralNetwork]: { - governanceAdmin?: AddressExtended; - maintenanceContract?: AddressExtended; - stakingVestingContract?: AddressExtended; - slashIndicatorContract?: AddressExtended; - stakingContract?: AddressExtended; - validatorContract?: AddressExtended; - roninTrustedOrganizationContract?: AddressExtended; - }; -} - -export interface MaintenanceArguments { - minMaintenanceBlockPeriod?: BigNumberish; - maxMaintenanceBlockPeriod?: BigNumberish; - minOffset?: BigNumberish; - maxSchedules?: BigNumberish; -} - -export interface RoninTrustedOrganizationArguments { - trustedOrganizations?: TrustedOrganizationStruct[]; - numerator?: BigNumberish; - denominator?: BigNumberish; -} - -export interface RoninTrustedOrganizationConfig { - [network: LiteralNetwork]: RoninTrustedOrganizationArguments | undefined; -} - -export interface MaintenanceConfig { - [network: LiteralNetwork]: MaintenanceArguments | undefined; -} - -export interface StakingArguments { - minValidatorBalance?: BigNumberish; -} - -export interface StakingConfig { - [network: LiteralNetwork]: StakingArguments | undefined; -} - -export interface StakingVestingArguments { - validatorBonusPerBlock?: BigNumberish; - bridgeOperatorBonusPerBlock?: BigNumberish; - topupAmount?: BigNumberish; -} - -export interface StakingVestingConfig { - [network: LiteralNetwork]: StakingVestingArguments | undefined; -} - -export interface SlashIndicatorArguments { - misdemeanorThreshold?: BigNumberish; - felonyThreshold?: BigNumberish; - bridgeVotingThreshold?: BigNumberish; - slashFelonyAmount?: BigNumberish; - slashDoubleSignAmount?: BigNumberish; - bridgeVotingSlashAmount?: BigNumberish; - felonyJailBlocks?: BigNumberish; - doubleSigningConstrainBlocks?: BigNumberish; -} - -export interface SlashIndicatorConfig { - [network: LiteralNetwork]: SlashIndicatorArguments | undefined; -} - -export interface RoninValidatorSetArguments { - maxValidatorNumber?: BigNumberish; - maxValidatorCandidate?: BigNumberish; - maxPrioritizedValidatorNumber?: BigNumberish; - numberOfBlocksInEpoch?: BigNumberish; -} - -export interface RoninValidatorSetConfig { - [network: LiteralNetwork]: RoninValidatorSetArguments | undefined; -} - -export interface RoninGovernanceAdminArguments { - bridgeContract?: Address; -} - -export interface RoninGovernanceAdminConfig { - [network: LiteralNetwork]: RoninGovernanceAdminArguments | undefined; -} - -export type MainchainGovernanceAdminArguments = RoninGovernanceAdminArguments & { - roleSetter?: Address; - relayers?: Address[]; +export const generalRoninConf: GeneralConfig = { + [Network.Hardhat]: { + startedAtBlock: 0, + bridgeContract: ethers.constants.AddressZero, + }, + [Network.Devnet]: { + startedAtBlock: 0, + bridgeContract: ethers.constants.AddressZero, + }, }; -export interface MainchainGovernanceAdminConfig { - [network: LiteralNetwork]: MainchainGovernanceAdminArguments | undefined; -} - -export const roninInitAddress: InitAddr = {}; -export const mainchainInitAddress: InitAddr = {}; +export const generalMainchainConf: GeneralConfig = { + [Network.Hardhat]: { + startedAtBlock: 0, + bridgeContract: ethers.constants.AddressZero, + }, + [Network.Devnet]: { + startedAtBlock: 0, + bridgeContract: ethers.constants.AddressZero, + }, +}; // TODO: update config for testnet & mainnet export const maintenanceConf: MaintenanceConfig = { @@ -166,14 +85,25 @@ export const stakingVestingConfig: StakingVestingConfig = { export const slashIndicatorConf: SlashIndicatorConfig = { [Network.Hardhat]: undefined, [Network.Devnet]: { - misdemeanorThreshold: 50, - felonyThreshold: 150, - bridgeVotingThreshold: 28800 * 3, // ~3 days - slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), // 1 RON - slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), // 10 RON - bridgeVotingSlashAmount: BigNumber.from(10).pow(18).mul(10_000), // 10.000 RON - felonyJailBlocks: 28800 * 2, // jails for 2 days - doubleSigningConstrainBlocks: 28800, + bridgeOperatorSlashing: { + missingVotesRatioTier1: 10_00, // 10% + missingVotesRatioTier2: 20_00, // 20% + jailDurationForMissingVotesRatioTier2: 28800 * 2, // jails for 2 days + }, + bridgeVotingSlashing: { + bridgeVotingThreshold: 28800 * 3, // ~3 days + bridgeVotingSlashAmount: BigNumber.from(10).pow(18).mul(10_000), // 10.000 RON + }, + doubleSignSlashing: { + slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), // 10 RON + doubleSigningJailUntilBlock: ethers.constants.MaxUint256, + }, + unavailabilitySlashing: { + unavailabilityTier1Threshold: 50, + unavailabilityTier2Threshold: 150, + slashAmountForUnavailabilityTier2Threshold: BigNumber.from(10).pow(18).mul(1), // 1 RON + jailDurationForUnavailabilityTier2Threshold: 2 * 28800, // jails for 2 days + }, }, [Network.Testnet]: undefined, [Network.Mainnet]: undefined, @@ -212,22 +142,12 @@ export const roninTrustedOrganizationConf: RoninTrustedOrganizationConfig = { [Network.Ethereum]: undefined, }; -// TODO: update config for testnet & mainnet -export const roninGovernanceAdminConf: RoninGovernanceAdminConfig = { - [Network.Hardhat]: undefined, - [Network.Devnet]: { - bridgeContract: ethers.constants.AddressZero, - }, - [Network.Goerli]: undefined, - [Network.Ethereum]: undefined, -}; - // TODO: update config for goerli, ethereum export const mainchainGovernanceAdminConf: MainchainGovernanceAdminConfig = { [Network.Hardhat]: undefined, [Network.Devnet]: { roleSetter: '0x93b8eed0a1e082ae2f478fd7f8c14b1fc0261bb1', - bridgeContract: ethers.constants.AddressZero, + // bridgeContract: ethers.constants.AddressZero, relayers: ['0x93b8eed0a1e082ae2f478fd7f8c14b1fc0261bb1'], }, [Network.Goerli]: undefined, diff --git a/src/deploy/calculate-address.ts b/src/deploy/calculate-address.ts index bdc9548d0..b3dc91958 100644 --- a/src/deploy/calculate-address.ts +++ b/src/deploy/calculate-address.ts @@ -1,7 +1,7 @@ import { ethers, network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { roninInitAddress, roninchainNetworks, mainchainNetworks, mainchainInitAddress } from '../config'; +import { generalRoninConf, roninchainNetworks, mainchainNetworks, generalMainchainConf } from '../config'; const calculateAddress = (from: string, nonce: number) => ({ nonce, @@ -13,7 +13,8 @@ const deploy = async ({ getNamedAccounts }: HardhatRuntimeEnvironment) => { let nonce = await ethers.provider.getTransactionCount(deployer); if (roninchainNetworks.includes(network.name!)) { - roninInitAddress[network.name] = { + generalRoninConf[network.name] = { + ...generalRoninConf[network.name], governanceAdmin: calculateAddress(deployer, nonce++), roninTrustedOrganizationContract: calculateAddress(deployer, nonce++), maintenanceContract: calculateAddress(deployer, nonce++), @@ -21,11 +22,13 @@ const deploy = async ({ getNamedAccounts }: HardhatRuntimeEnvironment) => { slashIndicatorContract: calculateAddress(deployer, nonce++), stakingContract: calculateAddress(deployer, nonce++), validatorContract: calculateAddress(deployer, nonce++), + bridgeTrackingContract: calculateAddress(deployer, nonce++), }; } if (mainchainNetworks.includes(network.name!)) { - mainchainInitAddress[network.name] = { + generalMainchainConf[network.name] = { + ...generalMainchainConf[network.name], governanceAdmin: calculateAddress(deployer, nonce++), roninTrustedOrganizationContract: calculateAddress(deployer, nonce++), }; @@ -40,6 +43,7 @@ deploy.dependencies = [ 'StakingLogic', 'RoninValidatorSetLogic', 'RoninTrustedOrganizationLogic', + 'BridgeTrackingLogic', ]; export default deploy; diff --git a/src/deploy/logic/bridge-tracking.ts b/src/deploy/logic/bridge-tracking.ts new file mode 100644 index 000000000..cf6ae2f21 --- /dev/null +++ b/src/deploy/logic/bridge-tracking.ts @@ -0,0 +1,23 @@ +import { network } from 'hardhat'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +import { roninchainNetworks } from '../../config'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!roninchainNetworks.includes(network.name!)) { + return; + } + + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + await deploy('BridgeTrackingLogic', { + contract: 'BridgeTracking', + from: deployer, + log: true, + }); +}; + +deploy.tags = ['BridgeTrackingLogic']; + +export default deploy; diff --git a/src/deploy/mainchain-governance-admin.ts b/src/deploy/mainchain-governance-admin.ts index b02e60625..54ad0eebf 100644 --- a/src/deploy/mainchain-governance-admin.ts +++ b/src/deploy/mainchain-governance-admin.ts @@ -1,7 +1,7 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { mainchainGovernanceAdminConf, mainchainInitAddress, mainchainNetworks } from '../config'; +import { mainchainGovernanceAdminConf, generalMainchainConf, mainchainNetworks } from '../config'; import { verifyAddress } from '../script/verify-address'; const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { @@ -17,16 +17,16 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme log: true, args: [ mainchainGovernanceAdminConf[network.name]?.roleSetter, - mainchainInitAddress[network.name].roninTrustedOrganizationContract?.address, - mainchainGovernanceAdminConf[network.name]?.bridgeContract, + generalMainchainConf[network.name].roninTrustedOrganizationContract?.address, + generalMainchainConf[network.name].bridgeContract, mainchainGovernanceAdminConf[network.name]?.relayers, ], - nonce: mainchainInitAddress[network.name].governanceAdmin?.nonce, + nonce: generalMainchainConf[network.name].governanceAdmin?.nonce, }); - verifyAddress(deployment.address, mainchainInitAddress[network.name].governanceAdmin?.address); + verifyAddress(deployment.address, generalMainchainConf[network.name].governanceAdmin?.address); }; deploy.tags = ['MainchainGovernanceAdmin']; -deploy.dependencies = ['RoninValidatorSetProxy']; +deploy.dependencies = ['BridgeTrackingProxy']; export default deploy; diff --git a/src/deploy/proxy/bonus-reward-proxy.ts b/src/deploy/proxy/bonus-reward-proxy.ts index 93166152d..f0abf65d4 100644 --- a/src/deploy/proxy/bonus-reward-proxy.ts +++ b/src/deploy/proxy/bonus-reward-proxy.ts @@ -2,7 +2,7 @@ import { BigNumber } from 'ethers'; import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { roninInitAddress, roninchainNetworks, stakingVestingConfig } from '../../config'; +import { generalRoninConf, roninchainNetworks, stakingVestingConfig } from '../../config'; import { verifyAddress } from '../../script/verify-address'; import { StakingVesting__factory } from '../../types'; @@ -17,7 +17,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const logicContract = await deployments.get('StakingVestingLogic'); const data = new StakingVesting__factory().interface.encodeFunctionData('initialize', [ - roninInitAddress[network.name]!.validatorContract?.address, + generalRoninConf[network.name]!.validatorContract?.address, stakingVestingConfig[network.name]!.validatorBonusPerBlock, stakingVestingConfig[network.name]!.bridgeOperatorBonusPerBlock, ]); @@ -26,11 +26,11 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, roninInitAddress[network.name]!.governanceAdmin?.address, data], + args: [logicContract.address, generalRoninConf[network.name]!.governanceAdmin?.address, data], value: BigNumber.from(stakingVestingConfig[network.name]!.topupAmount), - nonce: roninInitAddress[network.name].stakingVestingContract?.nonce, + nonce: generalRoninConf[network.name].stakingVestingContract?.nonce, }); - verifyAddress(deployment.address, roninInitAddress[network.name].stakingVestingContract?.address); + verifyAddress(deployment.address, generalRoninConf[network.name].stakingVestingContract?.address); }; deploy.tags = ['StakingVestingProxy']; diff --git a/src/deploy/proxy/bridge-tracking-proxy.ts b/src/deploy/proxy/bridge-tracking-proxy.ts new file mode 100644 index 000000000..d793c9208 --- /dev/null +++ b/src/deploy/proxy/bridge-tracking-proxy.ts @@ -0,0 +1,37 @@ +import { network } from 'hardhat'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +import { generalRoninConf, roninchainNetworks } from '../../config'; +import { verifyAddress } from '../../script/verify-address'; +import { BridgeTracking__factory } from '../../types'; + +const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { + if (!roninchainNetworks.includes(network.name!)) { + return; + } + + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + const logicContract = await deployments.get('BridgeTrackingLogic'); + + const data = new BridgeTracking__factory().interface.encodeFunctionData('initialize', [ + generalRoninConf[network.name]!.bridgeContract, + generalRoninConf[network.name]!.validatorContract?.address, + generalRoninConf[network.name]!.startedAtBlock, + ]); + + const deployment = await deploy('BridgeTrackingProxy', { + contract: 'TransparentUpgradeableProxyV2', + from: deployer, + log: true, + args: [logicContract.address, generalRoninConf[network.name]!.governanceAdmin?.address, data], + nonce: generalRoninConf[network.name].bridgeTrackingContract?.nonce, + }); + verifyAddress(deployment.address, generalRoninConf[network.name].bridgeTrackingContract?.address); +}; + +deploy.tags = ['BridgeTrackingProxy']; +deploy.dependencies = ['BridgeTrackingLogic', 'CalculateAddresses', 'RoninValidatorSetProxy']; + +export default deploy; diff --git a/src/deploy/proxy/mainchain-ronin-trusted-organization-proxy.ts b/src/deploy/proxy/mainchain-ronin-trusted-organization-proxy.ts index ac67e9335..24a96beba 100644 --- a/src/deploy/proxy/mainchain-ronin-trusted-organization-proxy.ts +++ b/src/deploy/proxy/mainchain-ronin-trusted-organization-proxy.ts @@ -1,7 +1,7 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { roninTrustedOrganizationConf, mainchainNetworks, mainchainInitAddress } from '../../config'; +import { roninTrustedOrganizationConf, mainchainNetworks, generalMainchainConf } from '../../config'; import { verifyAddress } from '../../script/verify-address'; import { RoninTrustedOrganization__factory } from '../../types'; @@ -24,10 +24,10 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, mainchainInitAddress[network.name].governanceAdmin?.address, data], - nonce: mainchainInitAddress[network.name].roninTrustedOrganizationContract?.nonce, + args: [logicContract.address, generalMainchainConf[network.name].governanceAdmin?.address, data], + nonce: generalMainchainConf[network.name].roninTrustedOrganizationContract?.nonce, }); - verifyAddress(deployment.address, mainchainInitAddress[network.name].roninTrustedOrganizationContract?.address); + verifyAddress(deployment.address, generalMainchainConf[network.name].roninTrustedOrganizationContract?.address); }; deploy.tags = ['MainchainRoninTrustedOrganizationProxy']; diff --git a/src/deploy/proxy/maintenance-proxy.ts b/src/deploy/proxy/maintenance-proxy.ts index 4244c25f3..ef3d05b2a 100644 --- a/src/deploy/proxy/maintenance-proxy.ts +++ b/src/deploy/proxy/maintenance-proxy.ts @@ -1,7 +1,7 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { maintenanceConf, roninInitAddress, roninchainNetworks } from '../../config'; +import { maintenanceConf, generalRoninConf, roninchainNetworks } from '../../config'; import { verifyAddress } from '../../script/verify-address'; import { Maintenance__factory } from '../../types'; @@ -16,7 +16,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const logicContract = await deployments.get('MaintenanceLogic'); const data = new Maintenance__factory().interface.encodeFunctionData('initialize', [ - roninInitAddress[network.name]!.validatorContract?.address, + generalRoninConf[network.name]!.validatorContract?.address, maintenanceConf[network.name]!.minMaintenanceBlockPeriod, maintenanceConf[network.name]!.maxMaintenanceBlockPeriod, maintenanceConf[network.name]!.minOffset, @@ -27,10 +27,10 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, roninInitAddress[network.name]!.governanceAdmin?.address, data], - nonce: roninInitAddress[network.name].maintenanceContract?.nonce, + args: [logicContract.address, generalRoninConf[network.name]!.governanceAdmin?.address, data], + nonce: generalRoninConf[network.name].maintenanceContract?.nonce, }); - verifyAddress(deployment.address, roninInitAddress[network.name].maintenanceContract?.address); + verifyAddress(deployment.address, generalRoninConf[network.name].maintenanceContract?.address); }; deploy.tags = ['MaintenanceProxy']; diff --git a/src/deploy/proxy/ronin-trusted-organization-proxy.ts b/src/deploy/proxy/ronin-trusted-organization-proxy.ts index b1cac63d7..06e012d29 100644 --- a/src/deploy/proxy/ronin-trusted-organization-proxy.ts +++ b/src/deploy/proxy/ronin-trusted-organization-proxy.ts @@ -1,7 +1,7 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { roninTrustedOrganizationConf, roninInitAddress, roninchainNetworks } from '../../config'; +import { roninTrustedOrganizationConf, generalRoninConf, roninchainNetworks } from '../../config'; import { verifyAddress } from '../../script/verify-address'; import { RoninTrustedOrganization__factory } from '../../types'; @@ -24,9 +24,9 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, roninInitAddress[network.name].governanceAdmin?.address, data], + args: [logicContract.address, generalRoninConf[network.name].governanceAdmin?.address, data], }); - verifyAddress(deployment.address, roninInitAddress[network.name].roninTrustedOrganizationContract?.address); + verifyAddress(deployment.address, generalRoninConf[network.name].roninTrustedOrganizationContract?.address); }; deploy.tags = ['RoninTrustedOrganizationProxy']; diff --git a/src/deploy/proxy/ronin-validator-proxy.ts b/src/deploy/proxy/ronin-validator-proxy.ts index a1edf539f..0af6f32ad 100644 --- a/src/deploy/proxy/ronin-validator-proxy.ts +++ b/src/deploy/proxy/ronin-validator-proxy.ts @@ -1,7 +1,7 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { roninValidatorSetConf, roninInitAddress, roninchainNetworks } from '../../config'; +import { roninValidatorSetConf, generalRoninConf, roninchainNetworks } from '../../config'; import { verifyAddress } from '../../script/verify-address'; import { RoninValidatorSet__factory } from '../../types'; @@ -12,15 +12,15 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const { deploy } = deployments; const { deployer } = await getNamedAccounts(); - const logicContract = await deployments.get('RoninValidatorSetLogic'); const data = new RoninValidatorSet__factory().interface.encodeFunctionData('initialize', [ - roninInitAddress[network.name]!.slashIndicatorContract?.address, - roninInitAddress[network.name]!.stakingContract?.address, - roninInitAddress[network.name]!.stakingVestingContract?.address, - roninInitAddress[network.name]!.maintenanceContract?.address, - roninInitAddress[network.name]!.roninTrustedOrganizationContract?.address, + generalRoninConf[network.name]!.slashIndicatorContract?.address, + generalRoninConf[network.name]!.stakingContract?.address, + generalRoninConf[network.name]!.stakingVestingContract?.address, + generalRoninConf[network.name]!.maintenanceContract?.address, + generalRoninConf[network.name]!.roninTrustedOrganizationContract?.address, + generalRoninConf[network.name]!.bridgeTrackingContract?.address, roninValidatorSetConf[network.name]!.maxValidatorNumber, roninValidatorSetConf[network.name]!.maxValidatorCandidate, roninValidatorSetConf[network.name]!.maxPrioritizedValidatorNumber, @@ -31,10 +31,10 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, roninInitAddress[network.name]!.governanceAdmin?.address, data], - nonce: roninInitAddress[network.name].validatorContract?.nonce, + args: [logicContract.address, generalRoninConf[network.name]!.governanceAdmin?.address, data], + nonce: generalRoninConf[network.name].validatorContract?.nonce, }); - verifyAddress(deployment.address, roninInitAddress[network.name].validatorContract?.address); + verifyAddress(deployment.address, generalRoninConf[network.name].validatorContract?.address); }; deploy.tags = ['RoninValidatorSetProxy']; diff --git a/src/deploy/proxy/slash-indicator-proxy.ts b/src/deploy/proxy/slash-indicator-proxy.ts index 006febc2f..fc508d9b6 100644 --- a/src/deploy/proxy/slash-indicator-proxy.ts +++ b/src/deploy/proxy/slash-indicator-proxy.ts @@ -1,7 +1,7 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { slashIndicatorConf, roninInitAddress, roninchainNetworks } from '../../config'; +import { slashIndicatorConf, generalRoninConf, roninchainNetworks } from '../../config'; import { verifyAddress } from '../../script/verify-address'; import { SlashIndicator__factory } from '../../types'; @@ -16,28 +16,39 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const logicContract = await deployments.get('SlashIndicatorLogic'); const data = new SlashIndicator__factory().interface.encodeFunctionData('initialize', [ - roninInitAddress[network.name]!.validatorContract?.address, - roninInitAddress[network.name]!.maintenanceContract?.address, - roninInitAddress[network.name]!.roninTrustedOrganizationContract?.address, - roninInitAddress[network.name]!.governanceAdmin?.address, - slashIndicatorConf[network.name]!.misdemeanorThreshold, - slashIndicatorConf[network.name]!.felonyThreshold, - slashIndicatorConf[network.name]!.bridgeVotingThreshold, - slashIndicatorConf[network.name]!.slashFelonyAmount, - slashIndicatorConf[network.name]!.slashDoubleSignAmount, - slashIndicatorConf[network.name]!.bridgeVotingSlashAmount, - slashIndicatorConf[network.name]!.felonyJailBlocks, - slashIndicatorConf[network.name]!.doubleSigningConstrainBlocks, + generalRoninConf[network.name]!.validatorContract?.address, + generalRoninConf[network.name]!.maintenanceContract?.address, + generalRoninConf[network.name]!.roninTrustedOrganizationContract?.address, + generalRoninConf[network.name]!.governanceAdmin?.address, + [ + slashIndicatorConf[network.name]!.bridgeOperatorSlashing?.missingVotesRatioTier1, + slashIndicatorConf[network.name]!.bridgeOperatorSlashing?.missingVotesRatioTier2, + slashIndicatorConf[network.name]!.bridgeOperatorSlashing?.jailDurationForMissingVotesRatioTier2, + ], + [ + slashIndicatorConf[network.name]!.bridgeVotingSlashing?.bridgeVotingThreshold, + slashIndicatorConf[network.name]!.bridgeVotingSlashing?.bridgeVotingSlashAmount, + ], + [ + slashIndicatorConf[network.name]!.doubleSignSlashing?.slashDoubleSignAmount, + slashIndicatorConf[network.name]!.doubleSignSlashing?.doubleSigningJailUntilBlock, + ], + [ + slashIndicatorConf[network.name]!.unavailabilitySlashing?.unavailabilityTier1Threshold, + slashIndicatorConf[network.name]!.unavailabilitySlashing?.unavailabilityTier2Threshold, + slashIndicatorConf[network.name]!.unavailabilitySlashing?.slashAmountForUnavailabilityTier2Threshold, + slashIndicatorConf[network.name]!.unavailabilitySlashing?.jailDurationForUnavailabilityTier2Threshold, + ], ]); const deployment = await deploy('SlashIndicatorProxy', { contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, roninInitAddress[network.name]!.governanceAdmin?.address, data], - nonce: roninInitAddress[network.name].slashIndicatorContract?.nonce, + args: [logicContract.address, generalRoninConf[network.name]!.governanceAdmin?.address, data], + nonce: generalRoninConf[network.name].slashIndicatorContract?.nonce, }); - verifyAddress(deployment.address, roninInitAddress[network.name].slashIndicatorContract?.address); + verifyAddress(deployment.address, generalRoninConf[network.name].slashIndicatorContract?.address); }; deploy.tags = ['SlashIndicatorProxy']; diff --git a/src/deploy/proxy/staking-proxy.ts b/src/deploy/proxy/staking-proxy.ts index 17c4504ac..38cb14b2d 100644 --- a/src/deploy/proxy/staking-proxy.ts +++ b/src/deploy/proxy/staking-proxy.ts @@ -1,7 +1,7 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { stakingConfig, roninInitAddress, roninchainNetworks } from '../../config'; +import { stakingConfig, generalRoninConf, roninchainNetworks } from '../../config'; import { verifyAddress } from '../../script/verify-address'; import { Staking__factory } from '../../types'; @@ -16,7 +16,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const logicContract = await deployments.get('StakingLogic'); const data = new Staking__factory().interface.encodeFunctionData('initialize', [ - roninInitAddress[network.name]!.validatorContract?.address, + generalRoninConf[network.name]!.validatorContract?.address, stakingConfig[network.name]!.minValidatorBalance, ]); @@ -24,10 +24,10 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme contract: 'TransparentUpgradeableProxyV2', from: deployer, log: true, - args: [logicContract.address, roninInitAddress[network.name]!.governanceAdmin?.address, data], - nonce: roninInitAddress[network.name].stakingContract?.nonce, + args: [logicContract.address, generalRoninConf[network.name]!.governanceAdmin?.address, data], + nonce: generalRoninConf[network.name].stakingContract?.nonce, }); - verifyAddress(deployment.address, roninInitAddress[network.name].stakingContract?.address); + verifyAddress(deployment.address, generalRoninConf[network.name].stakingContract?.address); }; deploy.tags = ['StakingProxy']; diff --git a/src/deploy/ronin-governance-admin.ts b/src/deploy/ronin-governance-admin.ts index 95157e663..ff25b9457 100644 --- a/src/deploy/ronin-governance-admin.ts +++ b/src/deploy/ronin-governance-admin.ts @@ -1,7 +1,7 @@ import { network } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { roninGovernanceAdminConf, roninchainNetworks, roninInitAddress } from '../config'; +import { roninchainNetworks, generalRoninConf } from '../config'; import { verifyAddress } from '../script/verify-address'; const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => { @@ -16,12 +16,12 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme from: deployer, log: true, args: [ - roninInitAddress[network.name].roninTrustedOrganizationContract?.address, - roninGovernanceAdminConf[network.name]?.bridgeContract, + generalRoninConf[network.name].roninTrustedOrganizationContract?.address, + generalRoninConf[network.name].bridgeContract, ], - nonce: roninInitAddress[network.name].governanceAdmin?.nonce, + nonce: generalRoninConf[network.name].governanceAdmin?.nonce, }); - verifyAddress(deployment.address, roninInitAddress[network.name].governanceAdmin?.address); + verifyAddress(deployment.address, generalRoninConf[network.name].governanceAdmin?.address); }; deploy.tags = ['RoninGovernanceAdmin']; diff --git a/src/utils.ts b/src/utils.ts index e733e8453..ce92af82e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,11 +1,143 @@ -import { BigNumber } from 'ethers'; +import { BigNumber, BigNumberish } from 'ethers'; +import { ethers } from 'hardhat'; +import { Address } from 'hardhat-deploy/dist/types'; + +import { TrustedOrganizationStruct } from './types/IRoninTrustedOrganization'; export const DEFAULT_ADDRESS = '0x0000000000000000000000000000000000000000'; export const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000'; +export enum Network { + Hardhat = 'hardhat', + Devnet = 'ronin-devnet', + Testnet = 'ronin-testnet', + Mainnet = 'ronin-mainnet', + Goerli = 'goerli', + Ethereum = 'ethereum', +} + +export type LiteralNetwork = Network | string; + +export const randomAddress = () => { + return new ethers.Wallet(ethers.utils.randomBytes(32)).address; +}; + export const randomBigNumber = () => { const hexString = Array.from(Array(64)) .map(() => Math.round(Math.random() * 0xf).toString(16)) .join(''); return BigNumber.from(`0x${hexString}`); }; + +export interface AddressExtended { + address: Address; + nonce?: number; +} + +export interface GeneralConfig { + [network: LiteralNetwork]: { + governanceAdmin?: AddressExtended; + maintenanceContract?: AddressExtended; + stakingVestingContract?: AddressExtended; + slashIndicatorContract?: AddressExtended; + stakingContract?: AddressExtended; + validatorContract?: AddressExtended; + roninTrustedOrganizationContract?: AddressExtended; + bridgeTrackingContract?: AddressExtended; + startedAtBlock: BigNumberish; + bridgeContract: Address; + }; +} + +export interface MaintenanceArguments { + minMaintenanceBlockPeriod?: BigNumberish; + maxMaintenanceBlockPeriod?: BigNumberish; + minOffset?: BigNumberish; + maxSchedules?: BigNumberish; +} + +export interface RoninTrustedOrganizationArguments { + trustedOrganizations?: TrustedOrganizationStruct[]; + numerator?: BigNumberish; + denominator?: BigNumberish; +} + +export interface RoninTrustedOrganizationConfig { + [network: LiteralNetwork]: RoninTrustedOrganizationArguments | undefined; +} + +export interface MaintenanceConfig { + [network: LiteralNetwork]: MaintenanceArguments | undefined; +} + +export interface StakingArguments { + minValidatorBalance?: BigNumberish; +} + +export interface StakingConfig { + [network: LiteralNetwork]: StakingArguments | undefined; +} + +export interface StakingVestingArguments { + validatorBonusPerBlock?: BigNumberish; + bridgeOperatorBonusPerBlock?: BigNumberish; + topupAmount?: BigNumberish; +} + +export interface StakingVestingConfig { + [network: LiteralNetwork]: StakingVestingArguments | undefined; +} + +export interface BridgeOperatorSlashingConfig { + missingVotesRatioTier1?: BigNumberish; + missingVotesRatioTier2?: BigNumberish; + jailDurationForMissingVotesRatioTier2?: BigNumberish; +} + +export interface BridgeVotingSlashingConfig { + bridgeVotingThreshold?: BigNumberish; + bridgeVotingSlashAmount?: BigNumberish; +} + +export interface DoubleSignSlashingConfig { + slashDoubleSignAmount?: BigNumberish; + doubleSigningJailUntilBlock?: BigNumberish; +} + +export interface UnavailabilitySlashing { + unavailabilityTier1Threshold?: BigNumberish; + unavailabilityTier2Threshold?: BigNumberish; + slashAmountForUnavailabilityTier2Threshold?: BigNumberish; + jailDurationForUnavailabilityTier2Threshold?: BigNumberish; +} + +export interface SlashIndicatorArguments { + bridgeOperatorSlashing?: BridgeOperatorSlashingConfig; + bridgeVotingSlashing?: BridgeVotingSlashingConfig; + doubleSignSlashing?: DoubleSignSlashingConfig; + unavailabilitySlashing?: UnavailabilitySlashing; +} + +export interface SlashIndicatorConfig { + [network: LiteralNetwork]: SlashIndicatorArguments | undefined; +} + +export interface RoninValidatorSetArguments { + maxValidatorNumber?: BigNumberish; + maxValidatorCandidate?: BigNumberish; + maxPrioritizedValidatorNumber?: BigNumberish; + numberOfBlocksInEpoch?: BigNumberish; +} + +export interface RoninValidatorSetConfig { + [network: LiteralNetwork]: RoninValidatorSetArguments | undefined; +} + +export interface MainchainGovernanceAdminArguments { + roleSetter?: Address; + relayers?: Address[]; +} + +export interface MainchainGovernanceAdminConfig { + [network: LiteralNetwork]: MainchainGovernanceAdminArguments | undefined; +} diff --git a/test/governance-admin/GovernanceAdmin.test.ts b/test/governance-admin/GovernanceAdmin.test.ts index f02c414bb..456b6c0f5 100644 --- a/test/governance-admin/GovernanceAdmin.test.ts +++ b/test/governance-admin/GovernanceAdmin.test.ts @@ -45,17 +45,22 @@ describe('Governance Admin test', () => { const { roninGovernanceAdminAddress, mainchainGovernanceAdminAddress, stakingContractAddress } = await initTest( 'RoninGovernanceAdmin.test' )({ - trustedOrganizations: governors.map((v) => ({ - consensusAddr: v.address, - governor: v.address, - bridgeVoter: v.address, - weight: 100, - addedBlock: 0, - })), - numerator: 1, - denominator: 2, - relayers: [relayer.address], bridgeContract: bridgeContract.address, + roninTrustedOrganizationArguments: { + trustedOrganizations: governors.map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), + + numerator: 1, + denominator: 2, + }, + mainchainGovernanceAdminArguments: { + relayers: [relayer.address], + }, }); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); diff --git a/test/helpers/fixture.ts b/test/helpers/fixture.ts index c2c3b28fa..bb7e4044e 100644 --- a/test/helpers/fixture.ts +++ b/test/helpers/fixture.ts @@ -1,27 +1,29 @@ -import { BigNumber } from 'ethers'; +import { BigNumber, BigNumberish } from 'ethers'; import { deployments, ethers, network } from 'hardhat'; import { Address } from 'hardhat-deploy/dist/types'; import { EpochController } from './ronin-validator-set'; import { - MainchainGovernanceAdminArguments, + generalMainchainConf, + generalRoninConf, mainchainGovernanceAdminConf, - MaintenanceArguments, maintenanceConf, - Network, - RoninGovernanceAdminArguments, - roninGovernanceAdminConf, - RoninTrustedOrganizationArguments, roninTrustedOrganizationConf, - RoninValidatorSetArguments, roninValidatorSetConf, - SlashIndicatorArguments, slashIndicatorConf, - StakingArguments, stakingConfig, - StakingVestingArguments, stakingVestingConfig, } from '../../src/config'; +import { + MainchainGovernanceAdminArguments, + MaintenanceArguments, + Network, + RoninTrustedOrganizationArguments, + RoninValidatorSetArguments, + SlashIndicatorArguments, + StakingArguments, + StakingVestingArguments, +} from '../../src/utils'; export interface InitTestOutput { roninGovernanceAdminAddress: Address; @@ -33,100 +35,137 @@ export interface InitTestOutput { stakingContractAddress: Address; stakingVestingContractAddress: Address; validatorContractAddress: Address; + bridgeTrackingAddress: Address; } -export const defaultTestConfig = { - felonyJailBlocks: 28800 * 2, - misdemeanorThreshold: 5, - felonyThreshold: 10, - slashFelonyAmount: BigNumber.from(10).pow(18).mul(1), - slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), - doubleSigningConstrainBlocks: 28800, - bridgeVotingThreshold: 28800 * 3, - bridgeVotingSlashAmount: BigNumber.from(10).pow(18).mul(10_000), +export interface InitTestInput { + bridgeContract?: Address; + startedAtBlock?: BigNumberish; + maintenanceArguments?: MaintenanceArguments; + stakingArguments?: StakingArguments; + stakingVestingArguments?: StakingVestingArguments; + slashIndicatorArguments?: SlashIndicatorArguments; + roninValidatorSetArguments?: RoninValidatorSetArguments; + roninTrustedOrganizationArguments?: RoninTrustedOrganizationArguments; + mainchainGovernanceAdminArguments?: MainchainGovernanceAdminArguments; +} - maxValidatorNumber: 4, - maxPrioritizedValidatorNumber: 0, - numberOfBlocksInEpoch: 600, +export const defaultTestConfig: InitTestInput = { + bridgeContract: ethers.constants.AddressZero, + startedAtBlock: 0, - minValidatorBalance: BigNumber.from(100), - maxValidatorCandidate: 10, + maintenanceArguments: { + minMaintenanceBlockPeriod: 100, + maxMaintenanceBlockPeriod: 1000, + minOffset: 200, + maxSchedules: 2, + }, - validatorBonusPerBlock: BigNumber.from(1), - bridgeOperatorBonusPerBlock: BigNumber.from(1), - topupAmount: BigNumber.from(100000000000), - minMaintenanceBlockPeriod: 100, - maxMaintenanceBlockPeriod: 1000, - minOffset: 200, - maxSchedules: 2, + stakingArguments: { + minValidatorBalance: BigNumber.from(100), + }, - trustedOrganizations: [], - numerator: 0, - denominator: 1, + stakingVestingArguments: { + validatorBonusPerBlock: 1, + bridgeOperatorBonusPerBlock: 1, + topupAmount: BigNumber.from(100000000000), + }, - roleSetter: ethers.constants.AddressZero, - bridgeContract: ethers.constants.AddressZero, - relayers: [], + slashIndicatorArguments: { + bridgeOperatorSlashing: { + missingVotesRatioTier1: 10_00, // 10% + missingVotesRatioTier2: 20_00, // 20% + jailDurationForMissingVotesRatioTier2: 28800 * 2, + }, + bridgeVotingSlashing: { + bridgeVotingThreshold: 28800 * 3, + bridgeVotingSlashAmount: BigNumber.from(10).pow(18).mul(10_000), + }, + doubleSignSlashing: { + slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), + doubleSigningJailUntilBlock: ethers.constants.MaxUint256, + }, + unavailabilitySlashing: { + unavailabilityTier1Threshold: 5, + unavailabilityTier2Threshold: 10, + slashAmountForUnavailabilityTier2Threshold: BigNumber.from(10).pow(18).mul(1), + jailDurationForUnavailabilityTier2Threshold: 28800 * 2, + }, + }, + + roninValidatorSetArguments: { + maxValidatorNumber: 4, + maxPrioritizedValidatorNumber: 0, + numberOfBlocksInEpoch: 600, + maxValidatorCandidate: 10, + }, + + roninTrustedOrganizationArguments: { + trustedOrganizations: [], + numerator: 0, + denominator: 1, + }, + + mainchainGovernanceAdminArguments: { + roleSetter: ethers.constants.AddressZero, + relayers: [], + }, }; export const initTest = (id: string) => - deployments.createFixture< - InitTestOutput, - MaintenanceArguments & - StakingArguments & - StakingVestingArguments & - SlashIndicatorArguments & - RoninValidatorSetArguments & - RoninTrustedOrganizationArguments & - RoninGovernanceAdminArguments & - MainchainGovernanceAdminArguments - >(async ({ deployments }, options) => { + deployments.createFixture(async ({ deployments }, options) => { if (network.name == Network.Hardhat) { + generalRoninConf[network.name] = { + ...generalRoninConf[network.name], + bridgeContract: options?.bridgeContract ?? defaultTestConfig.bridgeContract!, + startedAtBlock: options?.startedAtBlock ?? defaultTestConfig.startedAtBlock!, + }; + generalMainchainConf[network.name] = { + ...generalMainchainConf[network.name], + bridgeContract: options?.bridgeContract ?? defaultTestConfig.bridgeContract!, + startedAtBlock: options?.startedAtBlock ?? defaultTestConfig.startedAtBlock!, + }; maintenanceConf[network.name] = { - minMaintenanceBlockPeriod: options?.minMaintenanceBlockPeriod ?? defaultTestConfig.minMaintenanceBlockPeriod, - maxMaintenanceBlockPeriod: options?.maxMaintenanceBlockPeriod ?? defaultTestConfig.maxMaintenanceBlockPeriod, - minOffset: options?.minOffset ?? defaultTestConfig.minOffset, - maxSchedules: options?.maxSchedules ?? defaultTestConfig.maxSchedules, + ...defaultTestConfig?.maintenanceArguments, + ...options?.maintenanceArguments, }; slashIndicatorConf[network.name] = { - bridgeVotingThreshold: options?.bridgeVotingThreshold ?? defaultTestConfig.bridgeVotingThreshold, - bridgeVotingSlashAmount: options?.bridgeVotingSlashAmount ?? defaultTestConfig.bridgeVotingSlashAmount, - misdemeanorThreshold: options?.misdemeanorThreshold ?? defaultTestConfig.misdemeanorThreshold, - felonyThreshold: options?.felonyThreshold ?? defaultTestConfig.felonyThreshold, - slashFelonyAmount: options?.slashFelonyAmount ?? defaultTestConfig.slashFelonyAmount, - slashDoubleSignAmount: options?.slashDoubleSignAmount ?? defaultTestConfig.slashDoubleSignAmount, - felonyJailBlocks: options?.felonyJailBlocks ?? defaultTestConfig.felonyJailBlocks, - doubleSigningConstrainBlocks: - options?.doubleSigningConstrainBlocks ?? defaultTestConfig.doubleSigningConstrainBlocks, + bridgeOperatorSlashing: { + ...defaultTestConfig?.slashIndicatorArguments?.bridgeOperatorSlashing, + ...options?.slashIndicatorArguments?.bridgeOperatorSlashing, + }, + bridgeVotingSlashing: { + ...defaultTestConfig?.slashIndicatorArguments?.bridgeVotingSlashing, + ...options?.slashIndicatorArguments?.bridgeVotingSlashing, + }, + doubleSignSlashing: { + ...defaultTestConfig?.slashIndicatorArguments?.doubleSignSlashing, + ...options?.slashIndicatorArguments?.doubleSignSlashing, + }, + unavailabilitySlashing: { + ...defaultTestConfig?.slashIndicatorArguments?.unavailabilitySlashing, + ...options?.slashIndicatorArguments?.unavailabilitySlashing, + }, }; roninValidatorSetConf[network.name] = { - maxValidatorNumber: options?.maxValidatorNumber ?? defaultTestConfig.maxValidatorNumber, - maxValidatorCandidate: options?.maxValidatorCandidate ?? defaultTestConfig.maxValidatorCandidate, - maxPrioritizedValidatorNumber: - options?.maxPrioritizedValidatorNumber ?? defaultTestConfig.maxPrioritizedValidatorNumber, - numberOfBlocksInEpoch: options?.numberOfBlocksInEpoch ?? defaultTestConfig.numberOfBlocksInEpoch, + ...defaultTestConfig?.roninValidatorSetArguments, + ...options?.roninValidatorSetArguments, }; stakingConfig[network.name] = { - minValidatorBalance: options?.minValidatorBalance ?? defaultTestConfig.minValidatorBalance, + ...defaultTestConfig?.stakingArguments, + ...options?.stakingArguments, }; stakingVestingConfig[network.name] = { - validatorBonusPerBlock: options?.validatorBonusPerBlock ?? defaultTestConfig.validatorBonusPerBlock, - bridgeOperatorBonusPerBlock: - options?.bridgeOperatorBonusPerBlock ?? defaultTestConfig.bridgeOperatorBonusPerBlock, - topupAmount: options?.topupAmount ?? defaultTestConfig.topupAmount, + ...defaultTestConfig?.stakingVestingArguments, + ...options?.stakingVestingArguments, }; roninTrustedOrganizationConf[network.name] = { - trustedOrganizations: options?.trustedOrganizations ?? defaultTestConfig.trustedOrganizations, - numerator: options?.numerator ?? defaultTestConfig.numerator, - denominator: options?.denominator ?? defaultTestConfig.denominator, - }; - roninGovernanceAdminConf[network.name] = { - bridgeContract: options?.bridgeContract ?? defaultTestConfig.bridgeContract, + ...defaultTestConfig?.roninTrustedOrganizationArguments, + ...options?.roninTrustedOrganizationArguments, }; mainchainGovernanceAdminConf[network.name] = { - roleSetter: options?.roleSetter ?? defaultTestConfig.roleSetter, - bridgeContract: options?.bridgeContract ?? defaultTestConfig.bridgeContract, - relayers: options?.relayers ?? defaultTestConfig.relayers, + ...defaultTestConfig?.mainchainGovernanceAdminArguments, + ...options?.mainchainGovernanceAdminArguments, }; } @@ -134,6 +173,7 @@ export const initTest = (id: string) => 'CalculateAddresses', 'RoninGovernanceAdmin', 'RoninValidatorSetProxy', + 'BridgeTrackingProxy', 'SlashIndicatorProxy', 'StakingProxy', 'MaintenanceProxy', @@ -152,6 +192,7 @@ export const initTest = (id: string) => const stakingContractDeployment = await deployments.get('StakingProxy'); const stakingVestingContractDeployment = await deployments.get('StakingVestingProxy'); const validatorContractDeployment = await deployments.get('RoninValidatorSetProxy'); + const bridgeTrackingDeployment = await deployments.get('BridgeTrackingProxy'); await EpochController.setTimestampToPeriodEnding(); return { @@ -164,5 +205,6 @@ export const initTest = (id: string) => stakingContractAddress: stakingContractDeployment.address, stakingVestingContractAddress: stakingVestingContractDeployment.address, validatorContractAddress: validatorContractDeployment.address, + bridgeTrackingAddress: bridgeTrackingDeployment.address, }; }, id); diff --git a/test/helpers/ronin-validator-set.ts b/test/helpers/ronin-validator-set.ts index 8ec3fd5e8..81b5cd346 100644 --- a/test/helpers/ronin-validator-set.ts +++ b/test/helpers/ronin-validator-set.ts @@ -57,23 +57,6 @@ export class EpochController { } export const expects = { - emitRewardDeprecatedEvent: async function ( - tx: ContractTransaction, - expectingCoinbaseAddr: string, - expectingDeprecatedReward: BigNumberish - ) { - await expectEvent( - contractInterface, - 'RewardDeprecated', - tx, - (event) => { - expect(event.args[0], 'invalid coinbase address').eq(expectingCoinbaseAddr); - expect(event.args[1], 'invalid reward').eq(expectingDeprecatedReward); - }, - 1 - ); - }, - emitBlockRewardSubmittedEvent: async function ( tx: ContractTransaction, expectingCoinbaseAddr: string, @@ -115,6 +98,7 @@ export const expects = { emitBridgeOperatorRewardDistributedEvent: async function ( tx: ContractTransaction, expectingCoinbaseAddr: string, + expectingBridgeOperator: string, expectingRecipientAddr: string, expectingAmount: BigNumberish ) { @@ -124,8 +108,9 @@ export const expects = { tx, (event) => { expect(event.args[0], 'invalid coinbase address').eq(expectingCoinbaseAddr); - expect(event.args[1], 'invalid recipient address').eq(expectingRecipientAddr); - expect(event.args[2], 'invalid amount').eq(expectingAmount); + expect(event.args[1], 'invalid bridge operator').eq(expectingBridgeOperator); + expect(event.args[2], 'invalid recipient address').eq(expectingRecipientAddr); + expect(event.args[3], 'invalid amount').eq(expectingAmount); }, 1 ); @@ -167,30 +152,6 @@ export const expects = { ); }, - emitActivatedBlockProducersEvent: async function (tx: ContractTransaction, expectingProducers: string[]) { - await expectEvent( - contractInterface, - 'ActivatedBlockProducers', - tx, - (event) => { - expect(event.args[0], 'invalid activated producer set').eql(expectingProducers); - }, - 1 - ); - }, - - emitDeactivatedBlockProducersEvent: async function (tx: ContractTransaction, expectingProducers: string[]) { - await expectEvent( - contractInterface, - 'DeactivatedBlockProducers', - tx, - (event) => { - expect(event.args[0], 'invalid deactivated producer set').eql(expectingProducers); - }, - 1 - ); - }, - emitWrappedUpEpochEvent: async function (tx: ContractTransaction) { await expectEvent(contractInterface, 'WrappedUpEpoch', tx, () => {}, 1); }, diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index 5ece32adb..5ddf8b72d 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -33,10 +33,10 @@ let deployer: SignerWithAddress; let governor: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; -const felonyJailBlocks = 28800 * 2; -const misdemeanorThreshold = 10; -const felonyThreshold = 20; -const slashFelonyAmount = BigNumber.from(1); +const jailDurationForUnavailabilityTier2Threshold = 28800 * 2; +const unavailabilityTier1Threshold = 10; +const unavailabilityTier2Threshold = 20; +const slashAmountForUnavailabilityTier2Threshold = BigNumber.from(1); const slashDoubleSignAmount = 1000; const minValidatorBalance = BigNumber.from(100); @@ -47,21 +47,31 @@ describe('[Integration] Slash validators', () => { const { slashContractAddress, stakingContractAddress, validatorContractAddress, roninGovernanceAdminAddress } = await initTest('ActionSlashValidators')({ - felonyJailBlocks, - misdemeanorThreshold, - felonyThreshold, - slashFelonyAmount, - slashDoubleSignAmount, - minValidatorBalance, - trustedOrganizations: [ - { - consensusAddr: governor.address, - governor: governor.address, - bridgeVoter: governor.address, - weight: 100, - addedBlock: 0, + slashIndicatorArguments: { + unavailabilitySlashing: { + unavailabilityTier1Threshold, + unavailabilityTier2Threshold, + slashAmountForUnavailabilityTier2Threshold, + jailDurationForUnavailabilityTier2Threshold, }, - ], + doubleSignSlashing: { + slashDoubleSignAmount, + }, + }, + stakingArguments: { + minValidatorBalance, + }, + roninTrustedOrganizationArguments: { + trustedOrganizations: [ + { + consensusAddr: governor.address, + governor: governor.address, + bridgeVoter: governor.address, + weight: 100, + addedBlock: 0, + }, + ], + }, }); slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); @@ -97,15 +107,15 @@ describe('[Integration] Slash validators', () => { let slasheeIdx = 1; let slashee = validatorCandidates[slasheeIdx]; - for (let i = 0; i < misdemeanorThreshold - 1; i++) { - await slashContract.connect(coinbase).slash(slashee.address); + for (let i = 0; i < unavailabilityTier1Threshold - 1; i++) { + await slashContract.connect(coinbase).slashUnavailability(slashee.address); } - let tx = slashContract.connect(coinbase).slash(slashee.address); + let tx = slashContract.connect(coinbase).slashUnavailability(slashee.address); + await expect(tx).to.emit(slashContract, 'Slashed').withArgs(slashee.address, SlashType.MISDEMEANOR, period); await expect(tx) - .to.emit(slashContract, 'UnavailabilitySlashed') - .withArgs(slashee.address, SlashType.MISDEMEANOR, period); - await expect(tx).to.emit(validatorContract, 'ValidatorPunished').withArgs(slashee.address, 0, 0); + .to.emit(validatorContract, 'ValidatorPunished') + .withArgs(slashee.address, period, 0, 0, true, false); }); }); @@ -119,7 +129,7 @@ describe('[Integration] Slash validators', () => { before(async () => { slasheeIdx = 2; slashee = validatorCandidates[slasheeIdx]; - slasheeInitStakingAmount = minValidatorBalance.add(slashFelonyAmount.mul(10)); + slasheeInitStakingAmount = minValidatorBalance.add(slashAmountForUnavailabilityTier2Threshold.mul(10)); await stakingContract .connect(slashee) .applyValidatorCandidate(slashee.address, slashee.address, slashee.address, slashee.address, 2_00, { @@ -144,37 +154,44 @@ describe('[Integration] Slash validators', () => { }); it('Should the ValidatorSet contract emit event', async () => { - for (let i = 0; i < felonyThreshold - 1; i++) { - await slashContract.connect(coinbase).slash(slashee.address); + for (let i = 0; i < unavailabilityTier2Threshold - 1; i++) { + await slashContract.connect(coinbase).slashUnavailability(slashee.address); } - slashValidatorTx = await slashContract.connect(coinbase).slash(slashee.address); + slashValidatorTx = await slashContract.connect(coinbase).slashUnavailability(slashee.address); await expect(slashValidatorTx) - .to.emit(slashContract, 'UnavailabilitySlashed') + .to.emit(slashContract, 'Slashed') .withArgs(slashee.address, SlashType.FELONY, period); let blockNumber = await network.provider.send('eth_blockNumber'); await expect(slashValidatorTx) .to.emit(validatorContract, 'ValidatorPunished') - .withArgs(slashee.address, BigNumber.from(blockNumber).add(felonyJailBlocks), slashFelonyAmount); + .withArgs( + slashee.address, + period, + BigNumber.from(blockNumber).add(jailDurationForUnavailabilityTier2Threshold), + slashAmountForUnavailabilityTier2Threshold, + true, + false + ); }); it('Should the validator is put in jail', async () => { let blockNumber = await network.provider.send('eth_blockNumber'); expect(await validatorContract.getJailUntils(expectingValidatorSet)).eql([ - BigNumber.from(blockNumber).add(felonyJailBlocks), + BigNumber.from(blockNumber).add(jailDurationForUnavailabilityTier2Threshold), ]); }); it('Should the Staking contract emit Unstaked event', async () => { await expect(slashValidatorTx) .to.emit(stakingContract, 'Unstaked') - .withArgs(slashee.address, slashFelonyAmount); + .withArgs(slashee.address, slashAmountForUnavailabilityTier2Threshold); }); it('Should the Staking contract subtract staked amount from validator', async () => { - let _expectingSlasheeStakingAmount = slasheeInitStakingAmount.sub(slashFelonyAmount); + let _expectingSlasheeStakingAmount = slasheeInitStakingAmount.sub(slashAmountForUnavailabilityTier2Threshold); expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(_expectingSlasheeStakingAmount); }); @@ -256,37 +273,44 @@ describe('[Integration] Slash validators', () => { describe('Check effects on indicator and staked amount', async () => { it('Should the ValidatorSet contract emit event', async () => { - for (let i = 0; i < felonyThreshold - 1; i++) { - await slashContract.connect(coinbase).slash(slashee.address); + for (let i = 0; i < unavailabilityTier2Threshold - 1; i++) { + await slashContract.connect(coinbase).slashUnavailability(slashee.address); } - slashValidatorTx = await slashContract.connect(coinbase).slash(slashee.address); + slashValidatorTx = await slashContract.connect(coinbase).slashUnavailability(slashee.address); await expect(slashValidatorTx) - .to.emit(slashContract, 'UnavailabilitySlashed') + .to.emit(slashContract, 'Slashed') .withArgs(slashee.address, SlashType.FELONY, period); let blockNumber = await network.provider.send('eth_blockNumber'); await expect(slashValidatorTx) .to.emit(validatorContract, 'ValidatorPunished') - .withArgs(slashee.address, BigNumber.from(blockNumber).add(felonyJailBlocks), slashFelonyAmount); + .withArgs( + slashee.address, + period, + BigNumber.from(blockNumber).add(jailDurationForUnavailabilityTier2Threshold), + slashAmountForUnavailabilityTier2Threshold, + true, + false + ); }); it('Should the validator is put in jail', async () => { let blockNumber = await network.provider.send('eth_blockNumber'); expect(await validatorContract.getJailUntils([slashee.address])).eql([ - BigNumber.from(blockNumber).add(felonyJailBlocks), + BigNumber.from(blockNumber).add(jailDurationForUnavailabilityTier2Threshold), ]); }); it('Should the Staking contract emit Unstaked event', async () => { await expect(slashValidatorTx) .to.emit(stakingContract, 'Unstaked') - .withArgs(slashee.address, slashFelonyAmount); + .withArgs(slashee.address, slashAmountForUnavailabilityTier2Threshold); }); it('Should the Staking contract subtract staked amount from validator', async () => { - let _expectingSlasheeStakingAmount = slasheeInitStakingAmount.sub(slashFelonyAmount); + let _expectingSlasheeStakingAmount = slasheeInitStakingAmount.sub(slashAmountForUnavailabilityTier2Threshold); expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(_expectingSlasheeStakingAmount); }); }); @@ -370,7 +394,7 @@ describe('[Integration] Slash validators', () => { describe('Kicked candidates re-join', async () => { it('Should the kicked validators cannot top-up, since they are not candidates anymore', async () => { let topUpTx = stakingContract.connect(slashee).stake(slashee.address, { - value: slashFelonyAmount, + value: slashAmountForUnavailabilityTier2Threshold, }); await expect(topUpTx).revertedWith('StakingManager: query for non-existent pool'); diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index c01420432..461895384 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -30,8 +30,8 @@ let deployer: SignerWithAddress; let governor: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; -const felonyThreshold = 10; -const slashFelonyAmount = BigNumber.from(1); +const unavailabilityTier2Threshold = 10; +const slashAmountForUnavailabilityTier2Threshold = BigNumber.from(1); const slashDoubleSignAmount = 1000; const minValidatorBalance = BigNumber.from(100); const validatorBonusPerBlock = BigNumber.from(1); @@ -45,18 +45,30 @@ describe('[Integration] Submit Block Reward', () => { const { slashContractAddress, stakingContractAddress, validatorContractAddress, roninGovernanceAdminAddress } = await initTest('ActionSubmitReward')({ - felonyThreshold, - minValidatorBalance, - validatorBonusPerBlock, - slashFelonyAmount, - slashDoubleSignAmount, - trustedOrganizations: [governor].map((v) => ({ - consensusAddr: v.address, - governor: v.address, - bridgeVoter: v.address, - weight: 100, - addedBlock: 0, - })), + slashIndicatorArguments: { + unavailabilitySlashing: { + unavailabilityTier2Threshold, + slashAmountForUnavailabilityTier2Threshold, + }, + doubleSignSlashing: { + slashDoubleSignAmount, + }, + }, + stakingArguments: { + minValidatorBalance, + }, + stakingVestingArguments: { + validatorBonusPerBlock, + }, + roninTrustedOrganizationArguments: { + trustedOrganizations: [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), + }, }); slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); @@ -158,8 +170,8 @@ describe('[Integration] Submit Block Reward', () => { await validatorContract.connect(coinbase).wrapUpEpoch(); }); - for (let i = 0; i < felonyThreshold; i++) { - await slashContract.connect(coinbase).slash(validator.address); + for (let i = 0; i < unavailabilityTier2Threshold; i++) { + await slashContract.connect(coinbase).slashUnavailability(validator.address); } }); @@ -174,7 +186,7 @@ describe('[Integration] Submit Block Reward', () => { it('Should the ValidatorSetContract emit event of deprecating reward', async () => { await expect(submitRewardTx) - .to.emit(validatorContract, 'RewardDeprecated') + .to.emit(validatorContract, 'BlockRewardRewardDeprecated') .withArgs(validator.address, blockRewardAmount); }); diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index c8dbee5cb..96665191a 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -31,8 +31,8 @@ let deployer: SignerWithAddress; let governor: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; -const felonyThreshold = 10; -const slashFelonyAmount = BigNumber.from(1); +const unavailabilityTier2Threshold = 10; +const slashAmountForUnavailabilityTier2Threshold = BigNumber.from(1); const slashDoubleSignAmount = 1000; const minValidatorBalance = BigNumber.from(100); const maxValidatorNumber = 3; @@ -46,18 +46,30 @@ describe('[Integration] Wrap up epoch', () => { const { slashContractAddress, stakingContractAddress, validatorContractAddress, roninGovernanceAdminAddress } = await initTest('ActionWrapUpEpoch')({ - felonyThreshold, - slashFelonyAmount, - slashDoubleSignAmount, - minValidatorBalance, - maxValidatorNumber, - trustedOrganizations: [governor].map((v) => ({ - consensusAddr: v.address, - governor: v.address, - bridgeVoter: v.address, - weight: 100, - addedBlock: 0, - })), + slashIndicatorArguments: { + unavailabilitySlashing: { + unavailabilityTier2Threshold, + slashAmountForUnavailabilityTier2Threshold, + }, + doubleSignSlashing: { + slashDoubleSignAmount, + }, + }, + stakingArguments: { + minValidatorBalance, + }, + roninTrustedOrganizationArguments: { + trustedOrganizations: [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), + }, + roninValidatorSetArguments: { + maxValidatorNumber, + }, }); slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); @@ -177,7 +189,7 @@ describe('[Integration] Wrap up epoch', () => { describe('Wrap up epoch: at the end of the period', async () => { before(async () => { await validatorContract.addValidators(validators.map((v) => v.address)); - await Promise.all(validators.map((v) => slashContract.connect(coinbase).slash(v.address))); + await Promise.all(validators.map((v) => slashContract.connect(coinbase).slashUnavailability(v.address))); }); it('Should the ValidatorSet not reset counter, when the period is not ended', async () => { @@ -249,8 +261,8 @@ describe('[Integration] Wrap up epoch', () => { await validatorContract.wrapUpEpoch(); }); - for (let i = 0; i < felonyThreshold; i++) { - await slashContract.connect(coinbase).slash(slasheeAddress); + for (let i = 0; i < unavailabilityTier2Threshold; i++) { + await slashContract.connect(coinbase).slashUnavailability(slasheeAddress); } }); diff --git a/test/integration/Configuration.test.ts b/test/integration/Configuration.test.ts index 8e8ea47d0..147e6a400 100644 --- a/test/integration/Configuration.test.ts +++ b/test/integration/Configuration.test.ts @@ -14,176 +14,224 @@ import { Maintenance, StakingVesting__factory, StakingVesting, + RoninTrustedOrganization__factory, + RoninTrustedOrganization, + RoninGovernanceAdmin__factory, + RoninGovernanceAdmin, + BridgeTracking__factory, + BridgeTracking, } from '../../src/types'; -import { initTest } from '../helpers/fixture'; +import { initTest, InitTestInput } from '../helpers/fixture'; +import { randomAddress } from '../../src/utils'; let stakingVestingContract: StakingVesting; let maintenanceContract: Maintenance; let slashContract: SlashIndicator; let stakingContract: Staking; let validatorContract: RoninValidatorSet; +let roninTrustedOrganizationContract: RoninTrustedOrganization; +let roninGovernanceAdminContract: RoninGovernanceAdmin; +let bridgeTrackingContract: BridgeTracking; let coinbase: SignerWithAddress; let deployer: SignerWithAddress; let governor: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; -const felonyJailBlocks = 28800 * 2; -const misdemeanorThreshold = 5; -const felonyThreshold = 10; -const slashFelonyAmount = BigNumber.from(10).pow(18).mul(1); -const slashDoubleSignAmount = BigNumber.from(10).pow(18).mul(10); - -const maxValidatorNumber = 4; -const maxPrioritizedValidatorNumber = 0; -const numberOfBlocksInEpoch = 600; - -const minValidatorBalance = BigNumber.from(100); -const maxValidatorCandidate = 10; - -const validatorBonusPerBlock = BigNumber.from(1); -const topupAmount = BigNumber.from(10000); -const minMaintenanceBlockPeriod = 100; -const maxMaintenanceBlockPeriod = 1000; -const minOffset = 200; -const maxSchedules = 2; +const config: InitTestInput = { + bridgeContract: randomAddress(), + startedAtBlock: Math.floor(Math.random() * 1_000_000), + + maintenanceArguments: { + minMaintenanceBlockPeriod: 100, + maxMaintenanceBlockPeriod: 1000, + minOffset: 200, + maxSchedules: 2, + }, + + stakingArguments: { + minValidatorBalance: BigNumber.from(100), + }, + stakingVestingArguments: { + validatorBonusPerBlock: 1, + bridgeOperatorBonusPerBlock: 1, + topupAmount: BigNumber.from(10000), + }, + slashIndicatorArguments: { + bridgeOperatorSlashing: { + missingVotesRatioTier1: 10_00, // 10% + missingVotesRatioTier2: 20_00, // 20% + jailDurationForMissingVotesRatioTier2: 28800 * 2, + }, + bridgeVotingSlashing: { + bridgeVotingThreshold: 28800 * 3, + bridgeVotingSlashAmount: BigNumber.from(10).pow(18).mul(10_000), + }, + doubleSignSlashing: { + slashDoubleSignAmount: BigNumber.from(10).pow(18).mul(10), + doubleSigningJailUntilBlock: ethers.constants.MaxUint256, + }, + unavailabilitySlashing: { + unavailabilityTier1Threshold: 5, + unavailabilityTier2Threshold: 10, + slashAmountForUnavailabilityTier2Threshold: BigNumber.from(10).pow(18).mul(1), + jailDurationForUnavailabilityTier2Threshold: 28800 * 2, + }, + }, + roninValidatorSetArguments: { + maxValidatorNumber: 4, + maxPrioritizedValidatorNumber: 0, + numberOfBlocksInEpoch: 600, + maxValidatorCandidate: 10, + }, + roninTrustedOrganizationArguments: { + trustedOrganizations: [], + numerator: 0, + denominator: 1, + }, + mainchainGovernanceAdminArguments: { + roleSetter: ethers.constants.AddressZero, + relayers: [], + }, +}; describe('[Integration] Configuration check', () => { before(async () => { [coinbase, deployer, governor, ...validatorCandidates] = await ethers.getSigners(); + config.roninTrustedOrganizationArguments!.trustedOrganizations = [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })); const { maintenanceContractAddress, slashContractAddress, stakingContractAddress, validatorContractAddress, stakingVestingContractAddress, - } = await initTest('Configuration')({ - felonyJailBlocks, - misdemeanorThreshold, - felonyThreshold, - slashFelonyAmount, - slashDoubleSignAmount, - maxValidatorNumber, - maxPrioritizedValidatorNumber, - numberOfBlocksInEpoch, - minValidatorBalance, - maxValidatorCandidate, - validatorBonusPerBlock, - topupAmount, - minMaintenanceBlockPeriod, - maxMaintenanceBlockPeriod, - minOffset, - maxSchedules, - trustedOrganizations: [governor].map((v) => ({ - consensusAddr: v.address, - governor: v.address, - bridgeVoter: v.address, - weight: 100, - addedBlock: 0, - })), - }); + roninTrustedOrganizationAddress, + roninGovernanceAdminAddress, + bridgeTrackingAddress, + } = await initTest('Configuration')(config); - stakingVestingContract = StakingVesting__factory.connect(stakingVestingContractAddress, deployer); + roninGovernanceAdminContract = RoninGovernanceAdmin__factory.connect(roninGovernanceAdminAddress, deployer); maintenanceContract = Maintenance__factory.connect(maintenanceContractAddress, deployer); + roninTrustedOrganizationContract = RoninTrustedOrganization__factory.connect( + roninTrustedOrganizationAddress, + deployer + ); slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); + stakingVestingContract = StakingVesting__factory.connect(stakingVestingContractAddress, deployer); validatorContract = RoninValidatorSet__factory.connect(validatorContractAddress, deployer); + bridgeTrackingContract = BridgeTracking__factory.connect(bridgeTrackingAddress, deployer); }); - describe('Maintenance configuration', () => { - it('Should the MaintenanceContract config the validator contract correctly', async () => { - expect(await maintenanceContract.validatorContract()).eq(validatorContract.address); - }); - - it('Should the MaintenanceContract set the maintenance config correctly', async () => { - expect(await maintenanceContract.minMaintenanceBlockPeriod()).eq(minMaintenanceBlockPeriod); - expect(await maintenanceContract.maxMaintenanceBlockPeriod()).eq(maxMaintenanceBlockPeriod); - expect(await maintenanceContract.minOffset()).eq(minOffset); - expect(await maintenanceContract.maxSchedules()).eq(maxSchedules); - }); + it('Should the RoninGovernanceAdmin contract set configs correctly', async () => { + expect(await roninGovernanceAdminContract.roninTrustedOrganizationContract()).eq( + roninTrustedOrganizationContract.address + ); + expect(await roninGovernanceAdminContract.bridgeContract()).eq(config.bridgeContract); }); - describe('StakingVesting configuration', () => { - it('Should the StakingVestingContract config the validator contract correctly', async () => { - expect(await stakingVestingContract.validatorContract()).eq(validatorContract.address); - }); - - it('Should the StakingVestingContract config the block bonus correctly', async () => { - expect(await stakingVestingContract.validatorBlockBonus(0)).eq(validatorBonusPerBlock); - expect(await stakingVestingContract.validatorBlockBonus(Math.floor(Math.random() * 1_000_000))).eq( - validatorBonusPerBlock - ); - }); + it('Should the Maintenance contract set configs correctly', async () => { + expect(await maintenanceContract.validatorContract()).eq(validatorContract.address); + expect(await maintenanceContract.minMaintenanceBlockPeriod()).eq( + config.maintenanceArguments?.minMaintenanceBlockPeriod + ); + expect(await maintenanceContract.maxMaintenanceBlockPeriod()).eq( + config.maintenanceArguments?.maxMaintenanceBlockPeriod + ); + expect(await maintenanceContract.minOffset()).eq(config.maintenanceArguments!.minOffset); + expect(await maintenanceContract.maxSchedules()).eq(config.maintenanceArguments!.maxSchedules); }); - describe('ValidatorSetContract configuration', async () => { - it('Should the ValidatorSetContract config the StakingContract correctly', async () => { - let _stakingContract = await validatorContract.stakingContract(); - expect(_stakingContract).to.eq(stakingContract.address); - }); - - it('Should the ValidatorSetContract config the Slashing correctly', async () => { - let _slashingContract = await validatorContract.slashIndicatorContract(); - expect(_slashingContract).to.eq(slashContract.address); - }); - - it('Should config the maxValidatorNumber correctly', async () => { - let _maxValidatorNumber = await validatorContract.maxValidatorNumber(); - expect(_maxValidatorNumber).to.eq(maxValidatorNumber); - }); - - it('Should config the maxValidatorCandidate correctly', async () => { - let _maxValidatorCandidate = await validatorContract.maxValidatorCandidate(); - expect(_maxValidatorCandidate).to.eq(maxValidatorCandidate); - }); - - it('Should config the numberOfBlocksInEpoch correctly', async () => { - let _numberOfBlocksInEpoch = await validatorContract.numberOfBlocksInEpoch(); - expect(_numberOfBlocksInEpoch).to.eq(numberOfBlocksInEpoch); - }); + it('Should the RoninTrustedOrganization contract set configs correctly', async () => { + expect( + (await roninTrustedOrganizationContract.getAllTrustedOrganizations()).map( + ({ consensusAddr, governor, bridgeVoter, weight }) => ({ + consensusAddr, + governor, + bridgeVoter, + weight, + addedBlock: undefined, + }) + ) + ).eql( + [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: BigNumber.from(100), + addedBlock: undefined, + })) + ); + expect(await roninTrustedOrganizationContract.getThreshold()).eql( + [config.roninTrustedOrganizationArguments?.numerator, config.roninTrustedOrganizationArguments?.denominator].map( + BigNumber.from + ) + ); + }); + + it('Should the SlashIndicatorContract contract set configs correctly', async () => { + expect(await slashContract.validatorContract()).to.eq(validatorContract.address); + expect(await slashContract.maintenanceContract()).to.eq(maintenanceContract.address); + expect(await slashContract.roninTrustedOrganizationContract()).to.eq(roninTrustedOrganizationContract.address); + expect(await slashContract.roninGovernanceAdminContract()).to.eq(roninGovernanceAdminContract.address); + expect(await slashContract.getBridgeOperatorSlashingConfigs()).to.eql( + [ + config.slashIndicatorArguments?.bridgeOperatorSlashing?.missingVotesRatioTier1, + config.slashIndicatorArguments?.bridgeOperatorSlashing?.missingVotesRatioTier2, + config.slashIndicatorArguments?.bridgeOperatorSlashing?.jailDurationForMissingVotesRatioTier2, + ].map(BigNumber.from) + ); + }); + + it('Should the StakingContract contract set configs correctly', async () => { + expect(await stakingContract.validatorContract()).to.eq(validatorContract.address); + expect(await stakingContract.minValidatorBalance()).to.eq(config.stakingArguments?.minValidatorBalance); }); - describe('StakingContract configuration', async () => { - it('Should the StakingContract config the ValidatorSetContract correctly', async () => { - let _validatorSetContract = await stakingContract.validatorContract(); - expect(_validatorSetContract).to.eq(validatorContract.address); - }); + it('Should the StakingVestingContract contract set configs correctly', async () => { + expect(await stakingVestingContract.validatorContract()).eq(validatorContract.address); + expect(await stakingVestingContract.validatorBlockBonus(0)).eq( + config.stakingVestingArguments?.validatorBonusPerBlock + ); + expect(await stakingVestingContract.validatorBlockBonus(Math.floor(Math.random() * 1_000_000))).eq( + config.stakingVestingArguments?.validatorBonusPerBlock + ); + expect(await stakingVestingContract.bridgeOperatorBlockBonus(0)).eq( + config.stakingVestingArguments?.bridgeOperatorBonusPerBlock + ); + expect(await stakingVestingContract.bridgeOperatorBlockBonus(Math.floor(Math.random() * 1_000_000))).eq( + config.stakingVestingArguments?.bridgeOperatorBonusPerBlock + ); + }); - it('Should config the minValidatorBalance correctly', async () => { - let _minValidatorBalance = await stakingContract.minValidatorBalance(); - expect(_minValidatorBalance).to.eq(minValidatorBalance); - }); + it('Should the ValidatorSetContract contract set configs correctly', async () => { + expect(await validatorContract.slashIndicatorContract()).to.eq(slashContract.address); + expect(await validatorContract.stakingContract()).to.eq(stakingContract.address); + expect(await validatorContract.stakingVestingContract()).to.eq(stakingVestingContract.address); + expect(await validatorContract.maintenanceContract()).to.eq(maintenanceContract.address); + expect(await validatorContract.roninTrustedOrganizationContract()).to.eq(roninTrustedOrganizationContract.address); + expect(await validatorContract.bridgeTrackingContract()).to.eq(bridgeTrackingContract.address); + expect(await validatorContract.maxValidatorNumber()).to.eq(config.roninValidatorSetArguments?.maxValidatorNumber); + expect(await validatorContract.maxValidatorCandidate()).to.eq( + config.roninValidatorSetArguments?.maxValidatorCandidate + ); + expect(await validatorContract.maxPrioritizedValidatorNumber()).to.eq( + config.roninValidatorSetArguments?.maxPrioritizedValidatorNumber + ); + expect(await validatorContract.numberOfBlocksInEpoch()).to.eq( + config.roninValidatorSetArguments?.numberOfBlocksInEpoch + ); }); - describe('SlashIndicatorContract configuration', async () => { - it('Should the SlashIndicatorContract config the ValidatorSetContract correctly', async () => { - let _validatorSetContract = await slashContract.validatorContract(); - expect(_validatorSetContract).to.eq(validatorContract.address); - }); - - it('Should config the misdemeanorThreshold correctly', async () => { - let _misdemeanorThreshold = await slashContract.misdemeanorThreshold(); - expect(_misdemeanorThreshold).to.eq(misdemeanorThreshold); - }); - - it('Should config the felonyThreshold correctly', async () => { - let _felonyThreshold = await slashContract.felonyThreshold(); - expect(_felonyThreshold).to.eq(felonyThreshold); - }); - - it('Should config the slashFelonyAmount correctly', async () => { - let _slashFelonyAmount = await slashContract.slashFelonyAmount(); - expect(_slashFelonyAmount).to.eq(slashFelonyAmount); - }); - - it('Should config the slashDoubleSignAmount correctly', async () => { - let _slashDoubleSignAmount = await slashContract.slashDoubleSignAmount(); - expect(_slashDoubleSignAmount).to.eq(slashDoubleSignAmount); - }); - - it('Should config the felonyJailDuration correctly', async () => { - let _felonyJailDuration = await slashContract.felonyJailDuration(); - expect(_felonyJailDuration).to.eq(felonyJailBlocks); - }); + it('Should the BridgeTracking contract set configs correctly', async () => { + expect(await bridgeTrackingContract.bridgeContract()).to.eq(config.bridgeContract); + expect(await bridgeTrackingContract.validatorContract()).to.eq(validatorContract.address); + expect(await bridgeTrackingContract.startedAtBlock()).to.eq(config.startedAtBlock); }); }); diff --git a/test/maintainance/Maintenance.test.ts b/test/maintainance/Maintenance.test.ts index bc571ca24..edc4e834b 100644 --- a/test/maintainance/Maintenance.test.ts +++ b/test/maintainance/Maintenance.test.ts @@ -33,8 +33,8 @@ let governanceAdminInterface: GovernanceAdminInterface; let localEpochController: EpochController; -const misdemeanorThreshold = 50; -const felonyThreshold = 150; +const unavailabilityTier1Threshold = 50; +const unavailabilityTier2Threshold = 150; const maxValidatorNumber = 4; const numberOfBlocksInEpoch = 600; const minValidatorBalance = BigNumber.from(100); @@ -56,15 +56,33 @@ describe('Maintenance test', () => { validatorContractAddress, roninGovernanceAdminAddress, } = await initTest('Maintenance')({ - trustedOrganizations: [governor].map((v) => ({ - consensusAddr: v.address, - governor: v.address, - bridgeVoter: v.address, - weight: 100, - addedBlock: 0, - })), - misdemeanorThreshold, - felonyThreshold, + slashIndicatorArguments: { + unavailabilitySlashing: { + unavailabilityTier1Threshold, + unavailabilityTier2Threshold, + }, + }, + stakingArguments: { + minValidatorBalance, + }, + roninValidatorSetArguments: { + maxValidatorNumber, + numberOfBlocksInEpoch, + }, + maintenanceArguments: { + minOffset, + minMaintenanceBlockPeriod, + maxMaintenanceBlockPeriod, + }, + roninTrustedOrganizationArguments: { + trustedOrganizations: [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), + }, }); maintenanceContract = Maintenance__factory.connect(maintenanceContractAddress, deployer); slashContract = SlashIndicator__factory.connect(slashContractAddress, deployer); @@ -265,9 +283,9 @@ describe('Maintenance test', () => { }); it('[Slash Integration] Should not be able to slash the validator in maintenance time', async () => { - await slashContract.connect(coinbase).slash(validatorCandidates[0].address); + await slashContract.connect(coinbase).slashUnavailability(validatorCandidates[0].address); expect(await slashContract.currentUnavailabilityIndicator(validatorCandidates[0].address)).eq(0); - await slashContract.connect(coinbase).slash(validatorCandidates[1].address); + await slashContract.connect(coinbase).slashUnavailability(validatorCandidates[1].address); expect(await slashContract.currentUnavailabilityIndicator(validatorCandidates[1].address)).eq(0); }); diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index e190db35b..4f216143d 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -31,21 +31,19 @@ let validatorContract: RoninValidatorSet; let vagabond: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; let localIndicators: number[]; - let localEpochController: EpochController; -const misdemeanorThreshold = 5; -const felonyThreshold = 10; +const unavailabilityTier1Threshold = 5; +const unavailabilityTier2Threshold = 10; const maxValidatorNumber = 21; const maxValidatorCandidate = 50; const numberOfBlocksInEpoch = 600; const minValidatorBalance = BigNumber.from(100); -const slashFelonyAmount = BigNumber.from(2); +const slashAmountForUnavailabilityTier2Threshold = BigNumber.from(2); const slashDoubleSignAmount = BigNumber.from(5); const minOffset = 200; -const doubleSigningConstrainBlocks = BigNumber.from(28800); const increaseLocalCounterForValidatorAt = (idx: number, value?: number) => { value = value ?? 1; @@ -72,21 +70,36 @@ describe('Slash indicator test', () => { const { slashContractAddress, stakingContractAddress, validatorContractAddress, roninGovernanceAdminAddress } = await initTest('SlashIndicator')({ - trustedOrganizations: [governor].map((v) => ({ - consensusAddr: v.address, - governor: v.address, - bridgeVoter: v.address, - weight: 100, - addedBlock: 0, - })), - misdemeanorThreshold, - felonyThreshold, - maxValidatorNumber, - maxValidatorCandidate, - numberOfBlocksInEpoch, - minValidatorBalance, - slashFelonyAmount, - slashDoubleSignAmount, + slashIndicatorArguments: { + unavailabilitySlashing: { + unavailabilityTier1Threshold, + unavailabilityTier2Threshold, + slashAmountForUnavailabilityTier2Threshold, + }, + doubleSignSlashing: { + slashDoubleSignAmount, + }, + }, + stakingArguments: { + minValidatorBalance, + }, + roninValidatorSetArguments: { + maxValidatorNumber, + numberOfBlocksInEpoch, + maxValidatorCandidate, + }, + maintenanceArguments: { + minOffset, + }, + roninTrustedOrganizationArguments: { + trustedOrganizations: [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), + }, }); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); @@ -134,9 +147,9 @@ describe('Slash indicator test', () => { describe('Single flow test', async () => { describe('Unauthorized test', async () => { it('Should non-coinbase cannot call slash', async () => { - await expect(slashContract.connect(vagabond).slash(validatorCandidates[0].address)).to.revertedWith( - 'SlashIndicator: method caller must be coinbase' - ); + await expect( + slashContract.connect(vagabond).slashUnavailability(validatorCandidates[0].address) + ).to.revertedWith('SlashUnavailability: method caller must be coinbase'); }); }); @@ -149,15 +162,17 @@ describe('Slash indicator test', () => { let tx = await slashContract .connect(validatorCandidates[slasherIdx]) - .slash(validatorCandidates[slasheeIdx].address); - await expect(tx).to.not.emit(slashContract, 'UnavailabilitySlashed'); + .slashUnavailability(validatorCandidates[slasheeIdx].address); + await expect(tx).to.not.emit(slashContract, 'Slashed'); setLocalCounterForValidatorAt(slasheeIdx, 1); await validateIndicatorAt(slasheeIdx); }); it('Should validator not be able to slash themselves', async () => { const slasherIdx = 0; - await slashContract.connect(validatorCandidates[slasherIdx]).slash(validatorCandidates[slasherIdx].address); + await slashContract + .connect(validatorCandidates[slasherIdx]) + .slashUnavailability(validatorCandidates[slasherIdx].address); resetLocalCounterForValidatorAt(slasherIdx); await validateIndicatorAt(slasherIdx); @@ -167,8 +182,12 @@ describe('Slash indicator test', () => { const slasherIdx = 0; const slasheeIdx = 2; await network.provider.send('evm_setAutomine', [false]); - await slashContract.connect(validatorCandidates[slasherIdx]).slash(validatorCandidates[slasheeIdx].address); - let tx = slashContract.connect(validatorCandidates[slasherIdx]).slash(validatorCandidates[slasheeIdx].address); + await slashContract + .connect(validatorCandidates[slasherIdx]) + .slashUnavailability(validatorCandidates[slasheeIdx].address); + let tx = slashContract + .connect(validatorCandidates[slasherIdx]) + .slashUnavailability(validatorCandidates[slasheeIdx].address); await expect(tx).to.be.revertedWith( 'SlashIndicator: cannot slash a validator twice or slash more than one validator in one block' ); @@ -184,8 +203,12 @@ describe('Slash indicator test', () => { const slasheeIdx1 = 1; const slasheeIdx2 = 2; await network.provider.send('evm_setAutomine', [false]); - await slashContract.connect(validatorCandidates[slasherIdx]).slash(validatorCandidates[slasheeIdx1].address); - let tx = slashContract.connect(validatorCandidates[slasherIdx]).slash(validatorCandidates[slasheeIdx2].address); + await slashContract + .connect(validatorCandidates[slasherIdx]) + .slashUnavailability(validatorCandidates[slasheeIdx1].address); + let tx = slashContract + .connect(validatorCandidates[slasherIdx]) + .slashUnavailability(validatorCandidates[slasheeIdx2].address); await expect(tx).to.be.revertedWith( 'SlashIndicator: cannot slash a validator twice or slash more than one validator in one block' ); @@ -206,17 +229,17 @@ describe('Slash indicator test', () => { const slasheeIdx = 3; await network.provider.send('hardhat_setCoinbase', [validatorCandidates[slasherIdx].address]); - for (let i = 0; i < misdemeanorThreshold; i++) { + for (let i = 0; i < unavailabilityTier1Threshold; i++) { tx = await slashContract .connect(validatorCandidates[slasherIdx]) - .slash(validatorCandidates[slasheeIdx].address); + .slashUnavailability(validatorCandidates[slasheeIdx].address); } let period = await validatorContract.currentPeriod(); await expect(tx) - .to.emit(slashContract, 'UnavailabilitySlashed') + .to.emit(slashContract, 'Slashed') .withArgs(validatorCandidates[slasheeIdx].address, SlashType.MISDEMEANOR, period); - setLocalCounterForValidatorAt(slasheeIdx, misdemeanorThreshold); + setLocalCounterForValidatorAt(slasheeIdx, unavailabilityTier1Threshold); await validateIndicatorAt(slasheeIdx); }); @@ -228,11 +251,11 @@ describe('Slash indicator test', () => { tx = await slashContract .connect(validatorCandidates[slasherIdx]) - .slash(validatorCandidates[slasheeIdx].address); + .slashUnavailability(validatorCandidates[slasheeIdx].address); increaseLocalCounterForValidatorAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); - await expect(tx).not.to.emit(slashContract, 'UnavailabilitySlashed'); + await expect(tx).not.to.emit(slashContract, 'Slashed'); }); it('Should sync with validator set for felony (slash tier-2)', async () => { @@ -244,22 +267,22 @@ describe('Slash indicator test', () => { let period = await validatorContract.currentPeriod(); - for (let i = 0; i < felonyThreshold; i++) { + for (let i = 0; i < unavailabilityTier2Threshold; i++) { tx = await slashContract .connect(validatorCandidates[slasherIdx]) - .slash(validatorCandidates[slasheeIdx].address); + .slashUnavailability(validatorCandidates[slasheeIdx].address); - if (i == misdemeanorThreshold - 1) { + if (i == unavailabilityTier1Threshold - 1) { await expect(tx) - .to.emit(slashContract, 'UnavailabilitySlashed') + .to.emit(slashContract, 'Slashed') .withArgs(validatorCandidates[slasheeIdx].address, SlashType.MISDEMEANOR, period); } } await expect(tx) - .to.emit(slashContract, 'UnavailabilitySlashed') + .to.emit(slashContract, 'Slashed') .withArgs(validatorCandidates[slasheeIdx].address, SlashType.FELONY, period); - setLocalCounterForValidatorAt(slasheeIdx, felonyThreshold); + setLocalCounterForValidatorAt(slasheeIdx, unavailabilityTier2Threshold); await validateIndicatorAt(slasheeIdx); }); @@ -271,11 +294,11 @@ describe('Slash indicator test', () => { tx = await slashContract .connect(validatorCandidates[slasherIdx]) - .slash(validatorCandidates[slasheeIdx].address); + .slashUnavailability(validatorCandidates[slasheeIdx].address); increaseLocalCounterForValidatorAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); - await expect(tx).not.to.emit(slashContract, 'UnavailabilitySlashed'); + await expect(tx).not.to.emit(slashContract, 'Slashed'); }); }); @@ -283,11 +306,13 @@ describe('Slash indicator test', () => { it('Should the counter reset for one validator when the period ended', async () => { const slasherIdx = 0; const slasheeIdx = 5; - let numberOfSlashing = felonyThreshold - 1; + let numberOfSlashing = unavailabilityTier2Threshold - 1; await network.provider.send('hardhat_setCoinbase', [validatorCandidates[slasherIdx].address]); for (let i = 0; i < numberOfSlashing; i++) { - await slashContract.connect(validatorCandidates[slasherIdx]).slash(validatorCandidates[slasheeIdx].address); + await slashContract + .connect(validatorCandidates[slasherIdx]) + .slashUnavailability(validatorCandidates[slasheeIdx].address); } setLocalCounterForValidatorAt(slasheeIdx, numberOfSlashing); @@ -304,14 +329,14 @@ describe('Slash indicator test', () => { it('Should the counter reset for multiple validators when the period ended', async () => { const slasherIdx = 0; const slasheeIdxs = [6, 7, 8, 9, 10]; - let numberOfSlashing = felonyThreshold - 1; + let numberOfSlashing = unavailabilityTier2Threshold - 1; await network.provider.send('hardhat_setCoinbase', [validatorCandidates[slasherIdx].address]); for (let i = 0; i < numberOfSlashing; i++) { for (let j = 0; j < slasheeIdxs.length; j++) { await slashContract .connect(validatorCandidates[slasherIdx]) - .slash(validatorCandidates[slasheeIdxs[j]].address); + .slashUnavailability(validatorCandidates[slasheeIdxs[j]].address); } } @@ -335,10 +360,6 @@ describe('Slash indicator test', () => { let header1: BytesLike; let header2: BytesLike; - before(async () => { - await network.provider.send('hardhat_mine', [doubleSigningConstrainBlocks.toHexString(), '0x0']); - }); - it('Should not be able to slash themselves', async () => { const slasherIdx = 0; await network.provider.send('hardhat_setCoinbase', [validatorCandidates[slasherIdx].address]); @@ -350,7 +371,7 @@ describe('Slash indicator test', () => { .connect(validatorCandidates[slasherIdx]) .slashDoubleSign(validatorCandidates[slasherIdx].address, header1, header2); - await expect(tx).to.not.emit(slashContract, 'UnavailabilitySlashed'); + await expect(tx).to.not.emit(slashContract, 'Slashed'); }); it('Should be able to slash validator with double signing', async () => { @@ -367,7 +388,7 @@ describe('Slash indicator test', () => { let period = await validatorContract.currentPeriod(); await expect(tx) - .to.emit(slashContract, 'UnavailabilitySlashed') + .to.emit(slashContract, 'Slashed') .withArgs(validatorCandidates[slasheeIdx].address, SlashType.DOUBLE_SIGNING, period); }); }); diff --git a/test/validator/ArrangeValidators.test.ts b/test/validator/ArrangeValidators.test.ts index 7d547b381..c0887c735 100644 --- a/test/validator/ArrangeValidators.test.ts +++ b/test/validator/ArrangeValidators.test.ts @@ -27,7 +27,7 @@ let deployer: SignerWithAddress; let governor: SignerWithAddress; let candidates: Address[]; -const slashFelonyAmount = 100; +const slashAmountForUnavailabilityTier2Threshold = 100; const maxValidatorNumber = 7; const maxPrioritizedValidatorNumber = 4; const maxValidatorCandidate = 100; @@ -95,19 +95,26 @@ describe('Arrange validators', () => { roninTrustedOrganizationAddress, roninGovernanceAdminAddress, } = await initTest('ArrangeValidators')({ - maxValidatorNumber, - maxValidatorCandidate, - maxPrioritizedValidatorNumber, - slashFelonyAmount, - trustedOrganizations: [governor].map((v) => ({ - consensusAddr: v.address, - governor: v.address, - bridgeVoter: v.address, - weight: 100, - addedBlock: 0, - })), + slashIndicatorArguments: { + unavailabilitySlashing: { + slashAmountForUnavailabilityTier2Threshold, + }, + }, + roninValidatorSetArguments: { + maxValidatorCandidate, + maxValidatorNumber, + maxPrioritizedValidatorNumber, + }, + roninTrustedOrganizationArguments: { + trustedOrganizations: [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), + }, }); - validatorContract = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); slashIndicator = MockSlashIndicatorExtended__factory.connect(slashContractAddress, deployer); roninTrustedOrganization = RoninTrustedOrganization__factory.connect(roninTrustedOrganizationAddress, deployer); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index d5295a11f..a968c22ca 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -35,7 +35,7 @@ let currentValidatorSet: string[]; const localValidatorCandidatesLength = 5; -const slashFelonyAmount = 100; +const slashAmountForUnavailabilityTier2Threshold = 100; const maxValidatorNumber = 4; const maxValidatorCandidate = 100; const minValidatorBalance = BigNumber.from(20000); @@ -50,19 +50,31 @@ describe('Ronin Validator Set test', () => { const { slashContractAddress, validatorContractAddress, stakingContractAddress, roninGovernanceAdminAddress } = await initTest('RoninValidatorSet')({ - trustedOrganizations: [governor].map((v) => ({ - consensusAddr: v.address, - governor: v.address, - bridgeVoter: v.address, - weight: 100, - addedBlock: 0, - })), - minValidatorBalance, - maxValidatorNumber, - maxValidatorCandidate, - slashFelonyAmount, - validatorBonusPerBlock, - bridgeOperatorBonusPerBlock, + slashIndicatorArguments: { + unavailabilitySlashing: { + slashAmountForUnavailabilityTier2Threshold, + }, + }, + stakingArguments: { + minValidatorBalance, + }, + stakingVestingArguments: { + validatorBonusPerBlock, + bridgeOperatorBonusPerBlock, + }, + roninValidatorSetArguments: { + maxValidatorNumber, + maxValidatorCandidate, + }, + roninTrustedOrganizationArguments: { + trustedOrganizations: [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), + }, }); roninValidatorSet = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); @@ -240,14 +252,16 @@ describe('Ronin Validator Set test', () => { await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); await RoninValidatorSet.expects.emitStakingRewardDistributedEvent(tx!, 5049); // (5000 + 100) * 99% await RoninValidatorSet.expects.emitMiningRewardDistributedEvent(tx!, coinbase.address, treasury.address, 51); // (5000 + 100) * 1% - await RoninValidatorSet.expects.emitBridgeOperatorRewardDistributedEvent( - tx!, - coinbase.address, - treasury.address, - 37 - ); + expect(tx!) + .emit(roninValidatorSet, 'BridgeOperatorRewardDistributed') + .withArgs( + coinbase.address, + bridgeOperator.address, + treasury.address, + BigNumber.from(37).div(await roninValidatorSet.totalBridgeOperators()) + ); const balanceDiff = (await treasury.getBalance()).sub(balance); - expect(balanceDiff).eq(88); // = (5000 + 100) * 1% + 37 = (51 + 37) + expect(balanceDiff).eq(60); // = (5000 + 100) * 1% + 9 = (51 + 9) expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( 5049 // (5000 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase ); @@ -284,7 +298,8 @@ describe('Ronin Validator Set test', () => { tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); const balanceDiff = (await treasury.getBalance()).sub(balance); - expect(balanceDiff).eq(bridgeOperatorBonusPerBlock); // bridge bonus + const totalBridgeReward = bridgeOperatorBonusPerBlock.mul(2); // called submitBlockReward 2 times + expect(balanceDiff).eq(totalBridgeReward.div(await roninValidatorSet.totalBlockProducers())); expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( 5049 // (5000 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase ); @@ -310,7 +325,10 @@ describe('Ronin Validator Set test', () => { ); const balanceDiff = (await treasury.getBalance()).sub(balance); - const expectingBalanceDiff = validatorBonusPerBlock.add(100).div(100).add(bridgeOperatorBonusPerBlock); + const expectingBalanceDiff = validatorBonusPerBlock + .add(100) + .div(100) + .add(bridgeOperatorBonusPerBlock.div(await roninValidatorSet.totalBlockProducers())); expect(balanceDiff).eq(expectingBalanceDiff); expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( validatorBonusPerBlock.add(100).div(100).mul(99).mul(2) @@ -322,14 +340,14 @@ describe('Ronin Validator Set test', () => { const balance = await treasury.getBalance(); await slashIndicator.slashMisdemeanor(coinbase.address); tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); - await RoninValidatorSet.expects.emitRewardDeprecatedEvent(tx!, coinbase.address, 100); + await expect(tx).to.emit(roninValidatorSet, 'BlockRewardRewardDeprecated').withArgs(coinbase.address, 100); await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); const balanceDiff = (await treasury.getBalance()).sub(balance); - expect(balanceDiff).eq(0); + expect(balanceDiff).eq(bridgeOperatorBonusPerBlock.div(await roninValidatorSet.totalBlockProducers())); expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( validatorBonusPerBlock.add(100).div(100).mul(99).mul(2) ); From 5607fa9a0e1cfdeaf7670a2ef9244c5e11ecd74f Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 1 Nov 2022 13:46:31 +0700 Subject: [PATCH 180/190] [RoninValidatorSet] Update event & refactor test (#42) --- contracts/interfaces/IRoninValidatorSet.sol | 6 +- .../ronin/validator/RoninValidatorSet.sol | 42 ++++++------ test/helpers/ronin-validator-set.ts | 22 +++--- .../integration/ActionSlashValidators.test.ts | 32 ++++++--- test/integration/ActionWrapUpEpoch.test.ts | 6 +- test/maintainance/Maintenance.test.ts | 13 +++- test/validator/RoninValidatorSet.test.ts | 67 +++++++++++++------ 7 files changed, 127 insertions(+), 61 deletions(-) diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index 9bbe7f0b2..5a34969c3 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -12,11 +12,11 @@ interface IRoninValidatorSet is ICandidateManager { /// @dev Emitted when the number of blocks in epoch is updated event NumberOfBlocksInEpochUpdated(uint256); /// @dev Emitted when the validator set is updated - event ValidatorSetUpdated(address[]); + event ValidatorSetUpdated(uint256 indexed period, address[] consensusAddrs); /// @dev Emitted when the bridge operator set is updated, to mirror the in-jail and maintaining status of the validator. - event BlockProducerSetUpdated(address[]); + event BlockProducerSetUpdated(uint256 indexed period, address[] consensusAddrs); /// @dev Emitted when the bridge operator set is updated. - event BridgeOperatorSetUpdated(address[]); + event BridgeOperatorSetUpdated(uint256 indexed period, address[] bridgeOperators); /// @dev Emitted when the validator is punished. event ValidatorPunished( diff --git a/contracts/ronin/validator/RoninValidatorSet.sol b/contracts/ronin/validator/RoninValidatorSet.sol index a77a1a7bb..5efe08744 100644 --- a/contracts/ronin/validator/RoninValidatorSet.sol +++ b/contracts/ronin/validator/RoninValidatorSet.sol @@ -174,19 +174,19 @@ contract RoninValidatorSet is address[] memory _currentValidators = getValidators(); uint256 _epoch = epochOf(block.number); - uint256 _period = currentPeriod(); + uint256 _lastPeriod = currentPeriod(); if (_periodEnding) { uint256 _totalDelegatingReward = _distributeRewardToTreasuriesAndCalculateTotalDelegatingReward( - _period, + _lastPeriod, _currentValidators ); _settleAndTransferDelegatingRewards(_currentValidators, _totalDelegatingReward); - _currentValidators = _syncValidatorSet(); + _currentValidators = _syncValidatorSet(_newPeriod); } - _revampBlockProducers(_currentValidators); - emit WrappedUpEpoch(_period, _epoch, _periodEnding); + _revampBlockProducers(_newPeriod, _currentValidators); + emit WrappedUpEpoch(_lastPeriod, _epoch, _periodEnding); _lastUpdatedPeriod = _newPeriod; } @@ -439,16 +439,16 @@ contract RoninValidatorSet is * */ function _distributeRewardToTreasuriesAndCalculateTotalDelegatingReward( - uint256 _period, + uint256 _lastPeriod, address[] memory _currentValidators ) private returns (uint256 _totalDelegatingReward) { address _consensusAddr; address payable _treasury; IBridgeTracking _bridgeTracking = _bridgeTrackingContract; - uint256 _totalBridgeBallots = _bridgeTracking.totalBallots(_period); - uint256 _totalBridgeVotes = _bridgeTracking.totalVotes(_period); - uint256[] memory _bridgeBallots = _bridgeTracking.bulkTotalBallotsOf(_period, _currentValidators); + uint256 _totalBridgeBallots = _bridgeTracking.totalBallots(_lastPeriod); + uint256 _totalBridgeVotes = _bridgeTracking.totalVotes(_lastPeriod); + uint256[] memory _bridgeBallots = _bridgeTracking.bulkTotalBallotsOf(_lastPeriod, _currentValidators); ( uint256 _missingVotesRatioTier1, uint256 _missingVotesRatioTier2, @@ -459,7 +459,7 @@ contract RoninValidatorSet is _consensusAddr = _currentValidators[_i]; _treasury = _candidateInfo[_consensusAddr].treasuryAddr; _updateValidatorReward( - _period, + _lastPeriod, _consensusAddr, _bridgeBallots[_i], _totalBridgeVotes, @@ -469,11 +469,11 @@ contract RoninValidatorSet is _jailDurationForMissingVotesRatioTier2 ); - if (!_bridgeRewardDeprecated(_consensusAddr, _period)) { + if (!_bridgeRewardDeprecated(_consensusAddr, _lastPeriod)) { _distributeBridgeOperatingReward(_consensusAddr, _candidateInfo[_consensusAddr].bridgeOperatorAddr, _treasury); } - if (!_jailed(_consensusAddr) && !_miningRewardDeprecated(_consensusAddr, _period)) { + if (!_jailed(_consensusAddr) && !_miningRewardDeprecated(_consensusAddr, _lastPeriod)) { _totalDelegatingReward += _delegatingReward[_consensusAddr]; _distributeMiningReward(_consensusAddr, _treasury); } @@ -608,7 +608,7 @@ contract RoninValidatorSet is * Note: This method should be called once in the end of each period. * */ - function _syncValidatorSet() private returns (address[] memory _newValidators) { + function _syncValidatorSet(uint256 _newPeriod) private returns (address[] memory _newValidators) { uint256[] memory _balanceWeights; // This is a temporary approach since the slashing issue is still not finalized. // Read more about slashing issue at: https://www.notion.so/skymavis/Slashing-Issue-9610ae1452434faca1213ab2e1d7d944 @@ -623,8 +623,8 @@ contract RoninValidatorSet is _maxValidatorNumber, _maxPrioritizedValidatorNumber ); - _setNewValidatorSet(_newValidators, _newValidatorCount); - emit BridgeOperatorSetUpdated(getBridgeOperators()); + _setNewValidatorSet(_newValidators, _newValidatorCount, _newPeriod); + emit BridgeOperatorSetUpdated(_newPeriod, getBridgeOperators()); } /** @@ -635,7 +635,11 @@ contract RoninValidatorSet is * Note: This method should be called once in the end of each period. * */ - function _setNewValidatorSet(address[] memory _newValidators, uint256 _newValidatorCount) private { + function _setNewValidatorSet( + address[] memory _newValidators, + uint256 _newValidatorCount, + uint256 _newPeriod + ) private { for (uint256 _i = _newValidatorCount; _i < validatorCount; _i++) { delete _validatorMap[_validators[_i]]; delete _validators[_i]; @@ -656,7 +660,7 @@ contract RoninValidatorSet is } validatorCount = _count; - emit ValidatorSetUpdated(_newValidators); + emit ValidatorSetUpdated(_newPeriod, _newValidators); } /** @@ -668,7 +672,7 @@ contract RoninValidatorSet is * Emits the `BlockProducerSetUpdated` event. * */ - function _revampBlockProducers(address[] memory _currentValidators) private { + function _revampBlockProducers(uint256 _newPeriod, address[] memory _currentValidators) private { bool[] memory _maintainingList = _maintenanceContract.bulkMaintaining(_candidates, block.number + 1); for (uint _i = 0; _i < _currentValidators.length; _i++) { @@ -690,7 +694,7 @@ contract RoninValidatorSet is } } - emit BlockProducerSetUpdated(getBlockProducers()); + emit BlockProducerSetUpdated(_newPeriod, getBlockProducers()); } /////////////////////////////////////////////////////////////////////////////////////// diff --git a/test/helpers/ronin-validator-set.ts b/test/helpers/ronin-validator-set.ts index 81b5cd346..3cf6e25f0 100644 --- a/test/helpers/ronin-validator-set.ts +++ b/test/helpers/ronin-validator-set.ts @@ -128,31 +128,37 @@ export const expects = { ); }, - emitValidatorSetUpdatedEvent: async function (tx: ContractTransaction, expectingValidators: string[]) { + emitValidatorSetUpdatedEvent: async function ( + tx: ContractTransaction, + expectingPeriod: BigNumberish, + expectingValidators: string[] + ) { await expectEvent( contractInterface, 'ValidatorSetUpdated', tx, (event) => { - expect(event.args[0], 'invalid validator set').eql(expectingValidators); + expect(event.args[0], 'invalid period').eq(expectingPeriod); + expect(event.args[1], 'invalid validator set').eql(expectingValidators); }, 1 ); }, - emitBlockProducerSetUpdatedEvent: async function (tx: ContractTransaction, expectingBlockProducers: string[]) { + emitBlockProducerSetUpdatedEvent: async function ( + tx: ContractTransaction, + expectingPeriod: BigNumberish, + expectingBlockProducers: string[] + ) { await expectEvent( contractInterface, 'BlockProducerSetUpdated', tx, (event) => { - expect(event.args[0], 'invalid validator set').eql(expectingBlockProducers); + expect(event.args[0], 'invalid period').eq(expectingPeriod); + expect(event.args[1], 'invalid validator set').eql(expectingBlockProducers); }, 1 ); }, - - emitWrappedUpEpochEvent: async function (tx: ContractTransaction) { - await expectEvent(contractInterface, 'WrappedUpEpoch', tx, () => {}, 1); - }, }; diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index 5ddf8b72d..cca8357fb 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -147,7 +147,7 @@ describe('[Integration] Slash validators', () => { period = await validatorContract.currentPeriod(); expectingValidatorSet.push(slashee.address); expectingBlockProducerSet.push(slashee.address); - await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(wrapUpEpochTx!, expectingValidatorSet); + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(wrapUpEpochTx!, period, expectingValidatorSet); expect(await validatorContract.getValidators()).eql(expectingValidatorSet); expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); @@ -202,7 +202,11 @@ describe('[Integration] Slash validators', () => { }); expectingBlockProducerSet.pop(); expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); - await RoninValidatorSetExpects.emitBlockProducerSetUpdatedEvent(wrapUpEpochTx!, expectingBlockProducerSet); + await RoninValidatorSetExpects.emitBlockProducerSetUpdatedEvent( + wrapUpEpochTx!, + period, + expectingBlockProducerSet + ); expect(wrapUpEpochTx).not.emit(validatorContract, 'ValidatorSetUpdated'); }); @@ -233,7 +237,11 @@ describe('[Integration] Slash validators', () => { expectingBlockProducerSet.push(slashee.address); expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); - await RoninValidatorSetExpects.emitBlockProducerSetUpdatedEvent(wrapUpEpochTx!, expectingBlockProducerSet); + await RoninValidatorSetExpects.emitBlockProducerSetUpdatedEvent( + wrapUpEpochTx!, + period, + expectingBlockProducerSet + ); expect(wrapUpEpochTx).not.emit(validatorContract, 'ValidatorSetUpdated'); }); }); @@ -266,7 +274,7 @@ describe('[Integration] Slash validators', () => { period = await validatorContract.currentPeriod(); expectingValidatorSet.push(slashee.address); - await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(wrapUpEpochTx!, expectingValidatorSet); + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(wrapUpEpochTx!, period, expectingValidatorSet); expect(await validatorContract.getValidators()).eql(expectingValidatorSet); }); @@ -322,7 +330,11 @@ describe('[Integration] Slash validators', () => { wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); - await RoninValidatorSetExpects.emitBlockProducerSetUpdatedEvent(wrapUpEpochTx!, expectingBlockProducerSet); + await RoninValidatorSetExpects.emitBlockProducerSetUpdatedEvent( + wrapUpEpochTx!, + period, + expectingBlockProducerSet + ); }); it('Should the validator cannot re-join as a block producer when jail time is not over', async () => { @@ -352,7 +364,11 @@ describe('[Integration] Slash validators', () => { expectingBlockProducerSet.push(slashee.address); expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); - await RoninValidatorSetExpects.emitBlockProducerSetUpdatedEvent(wrapUpEpochTx!, expectingBlockProducerSet); + await RoninValidatorSetExpects.emitBlockProducerSetUpdatedEvent( + wrapUpEpochTx!, + period, + expectingBlockProducerSet + ); expect(wrapUpEpochTx).not.emit(validatorContract, 'ValidatorSetUpdated'); }); @@ -370,12 +386,12 @@ describe('[Integration] Slash validators', () => { wrapUpEpochTx = await validatorContract.connect(coinbase).wrapUpEpoch(); }); + period = await validatorContract.currentPeriod(); expectingRevokedCandidates = expectingValidatorSet.slice(-1); expectingBlockProducerSet.pop(); expectingValidatorSet.pop(); - await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(wrapUpEpochTx!, expectingValidatorSet); - + await RoninValidatorSetExpects.emitValidatorSetUpdatedEvent(wrapUpEpochTx!, period, expectingValidatorSet); expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); expect(await validatorContract.getValidators()).eql(expectingValidatorSet); }); diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index 96665191a..4938f4370 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -267,6 +267,8 @@ describe('[Integration] Wrap up epoch', () => { }); it('Should the block producer set get updated (excluding the slashed validator)', async () => { + const lastPeriod = await validatorContract.currentPeriod(); + const epoch = (await validatorContract.epochOf(await ethers.provider.getBlockNumber())).add(1); await mineBatchTxs(async () => { await validatorContract.endEpoch(); wrapUpTx = await validatorContract.wrapUpEpoch(); @@ -274,8 +276,8 @@ describe('[Integration] Wrap up epoch', () => { let expectingBlockProducerSet = [validators[2], validators[3]].map((_) => _.address).reverse(); - await ValidatorSetExpects.emitWrappedUpEpochEvent(wrapUpTx!); - await ValidatorSetExpects.emitBlockProducerSetUpdatedEvent(wrapUpTx!, expectingBlockProducerSet); + await expect(wrapUpTx!).emit(validatorContract, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, false); + await ValidatorSetExpects.emitBlockProducerSetUpdatedEvent(wrapUpTx!, lastPeriod, expectingBlockProducerSet); expect(await validatorContract.getValidators()).eql( [validators[1], validators[2], validators[3]].map((_) => _.address).reverse() diff --git a/test/maintainance/Maintenance.test.ts b/test/maintainance/Maintenance.test.ts index edc4e834b..67749c752 100644 --- a/test/maintainance/Maintenance.test.ts +++ b/test/maintainance/Maintenance.test.ts @@ -116,6 +116,7 @@ describe('Maintenance test', () => { let tx = await validatorContract.connect(coinbase).wrapUpEpoch(); await ValidatorSetExpects.emitValidatorSetUpdatedEvent( tx, + await validatorContract.currentPeriod(), validatorCandidates.map((_) => _.address) ); @@ -278,7 +279,11 @@ describe('Maintenance test', () => { await localEpochController.mineToBeforeEndOfEpoch(); let tx = await validatorContract.connect(coinbase).wrapUpEpoch(); let expectingBlockProducerSet = validatorCandidates.slice(2).map((_) => _.address); - await ValidatorSetExpects.emitBlockProducerSetUpdatedEvent(tx!, expectingBlockProducerSet); + await ValidatorSetExpects.emitBlockProducerSetUpdatedEvent( + tx!, + await validatorContract.currentPeriod(), + expectingBlockProducerSet + ); expect(await validatorContract.getBlockProducers()).eql(validatorCandidates.slice(2).map((_) => _.address)); }); @@ -293,7 +298,11 @@ describe('Maintenance test', () => { await localEpochController.mineToBeforeEndOfEpoch(); let tx = await validatorContract.connect(coinbase).wrapUpEpoch(); let expectingBlockProducerSet = validatorCandidates.map((_) => _.address); - await ValidatorSetExpects.emitBlockProducerSetUpdatedEvent(tx!, expectingBlockProducerSet); + await ValidatorSetExpects.emitBlockProducerSetUpdatedEvent( + tx!, + await validatorContract.currentPeriod(), + expectingBlockProducerSet + ); expect(await validatorContract.getBlockProducers()).eql(expectingBlockProducerSet); }); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index a968c22ca..7dd32b71d 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -32,6 +32,8 @@ let deployer: SignerWithAddress; let governor: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; let currentValidatorSet: string[]; +let lastPeriod: BigNumber; +let epoch: BigNumber; const localValidatorCandidatesLength = 5; @@ -111,12 +113,15 @@ describe('Ronin Validator Set test', () => { it('Should be able to wrap up epoch when the epoch is ending', async () => { let tx: ContractTransaction; + epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); + lastPeriod = await roninValidatorSet.currentPeriod(); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); - await RoninValidatorSet.expects.emitWrappedUpEpochEvent(tx!); - await RoninValidatorSet.expects.emitBlockProducerSetUpdatedEvent(tx!, []); + expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, true); + lastPeriod = await roninValidatorSet.currentPeriod(); + await RoninValidatorSet.expects.emitBlockProducerSetUpdatedEvent(tx!, lastPeriod, []); expect(await roninValidatorSet.getValidators()).eql([]); }); }); @@ -139,12 +144,12 @@ describe('Ronin Validator Set test', () => { } let tx: ContractTransaction; + epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); - - await RoninValidatorSet.expects.emitWrappedUpEpochEvent(tx!); + await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, false); expect(await roninValidatorSet.getValidators()).eql([]); expect(await roninValidatorSet.getBlockProducers()).eql([]); expect(tx!).not.emit(roninValidatorSet, 'ValidatorSetUpdated'); @@ -152,28 +157,32 @@ describe('Ronin Validator Set test', () => { }); describe('Wrapping up at the end of the period', async () => { - let _expectingValidatorsAddr: Address[]; + let expectingValidatorsAddr: Address[]; it('Should be able to wrap up epoch at end of period and sync validator set from staking contract', async () => { let tx: ContractTransaction; await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); + epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); + lastPeriod = await roninValidatorSet.currentPeriod(); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); - _expectingValidatorsAddr = validatorCandidates + expectingValidatorsAddr = validatorCandidates .slice(0, 4) .reverse() .map((_) => _.address); - await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, _expectingValidatorsAddr); - expect(await roninValidatorSet.getValidators()).eql(_expectingValidatorsAddr); - expect(await roninValidatorSet.getBlockProducers()).eql(_expectingValidatorsAddr); + await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, true); + lastPeriod = await roninValidatorSet.currentPeriod(); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, lastPeriod, expectingValidatorsAddr); + expect(await roninValidatorSet.getValidators()).eql(expectingValidatorsAddr); + expect(await roninValidatorSet.getBlockProducers()).eql(expectingValidatorsAddr); }); it('Should isValidator method returns `true` for validator', async () => { - for (let _validatorAddr of _expectingValidatorsAddr) { - expect(await roninValidatorSet.isValidator(_validatorAddr)).eq(true); + for (let validatorAddr of expectingValidatorsAddr) { + expect(await roninValidatorSet.isValidator(validatorAddr)).eq(true); } }); @@ -212,6 +221,8 @@ describe('Ronin Validator Set test', () => { let tx: ContractTransaction; await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); + epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); + lastPeriod = await roninValidatorSet.currentPeriod(); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); @@ -223,7 +234,9 @@ describe('Ronin Validator Set test', () => { .map((_) => _.address), ]; }); - await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); + await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, true); + lastPeriod = await roninValidatorSet.currentPeriod(); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, lastPeriod, currentValidatorSet); expect(await roninValidatorSet.getValidators()).eql(currentValidatorSet); expect(await roninValidatorSet.getBlockProducers()).eql(currentValidatorSet); }); @@ -245,11 +258,15 @@ describe('Ronin Validator Set test', () => { const balance = await treasury.getBalance(); let tx: ContractTransaction; await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); + lastPeriod = await roninValidatorSet.currentPeriod(); + epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); - await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); + await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, true); + lastPeriod = await roninValidatorSet.currentPeriod(); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, lastPeriod, currentValidatorSet); await RoninValidatorSet.expects.emitStakingRewardDistributedEvent(tx!, 5049); // (5000 + 100) * 99% await RoninValidatorSet.expects.emitMiningRewardDistributedEvent(tx!, coinbase.address, treasury.address, 51); // (5000 + 100) * 1% expect(tx!) @@ -275,6 +292,8 @@ describe('Ronin Validator Set test', () => { tx = await slashIndicator.slashMisdemeanor(coinbase.address); expect(tx).emit(roninValidatorSet, 'ValidatorPunished').withArgs(coinbase.address, 0, 0); + epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); + lastPeriod = await roninValidatorSet.currentPeriod(); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); @@ -285,7 +304,7 @@ describe('Ronin Validator Set test', () => { expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( 5049 // (5000 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase ); - await RoninValidatorSet.expects.emitWrappedUpEpochEvent(tx!); + await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, false); expect(tx!).not.emit(roninValidatorSet, 'ValidatorSetUpdated'); } @@ -293,6 +312,8 @@ describe('Ronin Validator Set test', () => { const balance = await treasury.getBalance(); await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); + epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); + lastPeriod = await roninValidatorSet.currentPeriod(); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); @@ -303,8 +324,9 @@ describe('Ronin Validator Set test', () => { expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( 5049 // (5000 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase ); - await RoninValidatorSet.expects.emitWrappedUpEpochEvent(tx!); - await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); + await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, true); + lastPeriod = await roninValidatorSet.currentPeriod(); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, lastPeriod, currentValidatorSet); } }); @@ -313,12 +335,15 @@ describe('Ronin Validator Set test', () => { const balance = await treasury.getBalance(); await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); + epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); + lastPeriod = await roninValidatorSet.currentPeriod(); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); - await RoninValidatorSet.expects.emitWrappedUpEpochEvent(tx!); - await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); + await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, true); + lastPeriod = await roninValidatorSet.currentPeriod(); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, lastPeriod, currentValidatorSet); await RoninValidatorSet.expects.emitStakingRewardDistributedEvent( tx!, validatorBonusPerBlock.add(100).div(100).mul(99) @@ -342,6 +367,8 @@ describe('Ronin Validator Set test', () => { tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); await expect(tx).to.emit(roninValidatorSet, 'BlockRewardRewardDeprecated').withArgs(coinbase.address, 100); await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); + epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); + lastPeriod = await roninValidatorSet.currentPeriod(); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); @@ -351,7 +378,9 @@ describe('Ronin Validator Set test', () => { expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( validatorBonusPerBlock.add(100).div(100).mul(99).mul(2) ); - await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, currentValidatorSet); + await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, true); + lastPeriod = await roninValidatorSet.currentPeriod(); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, lastPeriod, currentValidatorSet); }); }); }); From 4bfcd66132a2bcd64d887e57e052fe8b3d8cfe8e Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Tue, 1 Nov 2022 13:47:23 +0700 Subject: [PATCH 181/190] Fix undelegate from deprecated candidates issue (#41) PSC-93 Fix undelegate from ex-candidate issue --- contracts/interfaces/IStaking.sol | 14 +++--- contracts/ronin/staking/Staking.sol | 2 +- test/staking/Staking.test.ts | 73 ++++++++++++++++++++++++++--- 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index d7b1c1c66..69e0eccfd 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -25,13 +25,13 @@ interface IStaking is IRewardPool { /// @dev Emitted when the staked amount is deprecated. event StakedAmountDeprecated(address indexed validator, address indexed admin, uint256 amount); /// @dev Emitted when the pool admin staked for themself. - event Staked(address indexed validator, uint256 amount); + event Staked(address indexed consensuAddr, uint256 amount); /// @dev Emitted when the pool admin unstaked the amount of RON from themself. - event Unstaked(address indexed validator, uint256 amount); - /// @dev Emitted when the delegator staked for a validator. - event Delegated(address indexed delegator, address indexed validator, uint256 amount); - /// @dev Emitted when the delegator unstaked from a validator. - event Undelegated(address indexed delegator, address indexed validator, uint256 amount); + event Unstaked(address indexed consensuAddr, uint256 amount); + /// @dev Emitted when the delegator staked for a validator candidate. + event Delegated(address indexed delegator, address indexed consensuAddr, uint256 amount); + /// @dev Emitted when the delegator unstaked from a validator candidate. + event Undelegated(address indexed delegator, address indexed consensuAddr, uint256 amount); /// @dev Emitted when the minimum balance for being a validator is updated. event MinValidatorBalanceUpdated(uint256 threshold); @@ -176,7 +176,7 @@ interface IStaking is IRewardPool { * - The method caller is the pool admin. * */ - function requestRenounce(address consensusAddr) external; + function requestRenounce(address _consensusAddr) external; /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR DELEGATOR // diff --git a/contracts/ronin/staking/Staking.sol b/contracts/ronin/staking/Staking.sol index 7c15a1f4f..6ec6a2b5e 100644 --- a/contracts/ronin/staking/Staking.sol +++ b/contracts/ronin/staking/Staking.sol @@ -116,7 +116,7 @@ contract Staking is IStaking, StakingManager, Initializable { } } - delete _stakingPool[_pool.addr]; + delete _stakingPool[_pool.addr].stakedAmount; } emit PoolsDeprecated(_pools); diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index 51dea30b9..4285f9101 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -111,6 +111,14 @@ describe('Staking test', () => { tx = await stakingContract.connect(poolAddr).unstake(poolAddr.address, 1); await expect(tx!).emit(stakingContract, 'Unstaked').withArgs(poolAddr.address, 1); expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance.mul(2)); + expect(await stakingContract.balanceOf(poolAddr.address, poolAddr.address)).eq(minValidatorBalance.mul(2)); + }); + + it('[Delegator] Should be able to delegate/undelegate to a validator candidate', async () => { + await stakingContract.delegate(poolAddr.address, { value: 10 }); + expect(await stakingContract.balanceOf(poolAddr.address, deployer.address)).eq(10); + await stakingContract.undelegate(poolAddr.address, 1); + expect(await stakingContract.balanceOf(poolAddr.address, deployer.address)).eq(9); }); it('Should be not able to unstake with the balance left is not larger than the minimum balance threshold', async () => { @@ -145,7 +153,7 @@ describe('Staking test', () => { expect(await stakingContract.getStakingPool(poolAddr.address)).eql([ poolAddr.address, stakedAmount, - stakedAmount, + stakedAmount.add(9), ]); await expect(() => validatorContract.wrapUpEpoch()).changeEtherBalance(poolAddr, stakedAmount); await expect(stakingContract.getStakingPool(poolAddr.address)).revertedWith( @@ -159,6 +167,17 @@ describe('Staking test', () => { otherPoolAddr = validatorCandidates[2]; }); + it('Should be able to undelegate from a deprecated validator candidate', async () => { + await stakingContract.undelegate(poolAddr.address, 1); + expect(await stakingContract.balanceOf(poolAddr.address, deployer.address)).eq(8); + }); + + it('Should not be able to delegate to a deprecated pool', async () => { + await expect(stakingContract.delegate(poolAddr.address, { value: 1 })).revertedWith( + 'StakingManager: query for non-existent pool' + ); + }); + it('Should not be able to delegate with empty value', async () => { await expect(stakingContract.delegate(otherPoolAddr.address)).revertedWith( 'StakingManager: query with empty value' @@ -174,12 +193,6 @@ describe('Staking test', () => { ); }); - it('Should not be able to delegate to a deprecated pool', async () => { - await expect(stakingContract.delegate(poolAddr.address, { value: 1 })).revertedWith( - 'StakingManager: query for non-existent pool' - ); - }); - it('Should be able to delegate/undelegate', async () => { let tx: ContractTransaction; tx = await stakingContract.connect(userA).delegate(otherPoolAddr.address, { value: 1 }); @@ -194,5 +207,51 @@ describe('Staking test', () => { await expect(tx!).emit(stakingContract, 'Undelegated').withArgs(userA.address, otherPoolAddr.address, 1); expect(await stakingContract.totalBalance(otherPoolAddr.address)).eq(minValidatorBalance.mul(2).add(1)); }); + + it('Should not be able to undelegate with empty amount', async () => { + await expect(stakingContract.undelegate(otherPoolAddr.address, 0)).revertedWith('StakingManager: invalid amount'); + }); + + it('Should not be able to undelegate more than the delegated amount', async () => { + await expect(stakingContract.undelegate(otherPoolAddr.address, 1000)).revertedWith( + 'StakingManager: insufficient amount to undelegate' + ); + }); + + it('[Validator Candidate] Should an ex-candidate to rejoin Staking contract', async () => { + await stakingContract + .connect(poolAddr) + .applyValidatorCandidate( + poolAddr.address, + poolAddr.address, + poolAddr.address, + poolAddr.address, + 2, + /* 0.02% */ { value: minValidatorBalance } + ); + expect(await stakingContract.getStakingPool(poolAddr.address)).eql([ + poolAddr.address, + minValidatorBalance, + minValidatorBalance.add(8), + ]); + expect(await stakingContract.balanceOf(poolAddr.address, deployer.address)).eq(8); + }); + + it('Should be able to delegate/undelegate for the rejoined candidate', async () => { + await stakingContract.delegate(poolAddr.address, { value: 2 }); + expect(await stakingContract.balanceOf(poolAddr.address, deployer.address)).eq(10); + + await stakingContract.connect(userA).delegate(poolAddr.address, { value: 2 }); + await stakingContract.connect(userB).delegate(poolAddr.address, { value: 2 }); + expect( + await stakingContract.bulkBalanceOf([poolAddr.address, poolAddr.address], [userA.address, userB.address]) + ).eql([2, 2].map(BigNumber.from)); + + await stakingContract.connect(userA).undelegate(poolAddr.address, 2); + await stakingContract.connect(userB).undelegate(poolAddr.address, 1); + expect( + await stakingContract.bulkBalanceOf([poolAddr.address, poolAddr.address], [userA.address, userB.address]) + ).eql([0, 1].map(BigNumber.from)); + }); }); }); From 98c85b3ce4eb1c696b39a106cf15dc79afe274de Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Wed, 2 Nov 2022 14:43:02 +0700 Subject: [PATCH 182/190] Feat/credit score and bail out rebased (#43) * Add draft contract for credit score and bail out * Fix maintenance at current period * Update comment * Fix check in maintainingAtCurrentPeriod * Replace maintainingAtCurrentPeriod by maintainingInBlockRange * Refactor credit score to an abstract contract * Fix bailout function * Refactor test for slash * Fix contract * Refactor slash indicator test * Add first test * Fix fixture id * Add more test for credit score * Fix contracts * Fix test helper. Add more tests. * Add jailedAtBlock methods * Fix test * Fix rebase error. Update contracts. * Fix contracts * Fix credit score test * Fix test * Refactor _setUnavailabilityIndicator method * Change fixture name * Fix docs * Add gap for CreditScore contract * Mark helper contracts of SlashIndicator as abstract * Refactor slash indicator interfaces * Update contracts/interfaces/slash-indicator/ICreditScore.sol Co-authored-by: Duc Tho Tran * Update contracts/interfaces/slash-indicator/ICreditScore.sol Co-authored-by: Duc Tho Tran * Update test/helpers/ronin-validator-set.ts Co-authored-by: Duc Tho Tran * Update contracts/libraries/Math.sol Co-authored-by: Duc Tho Tran * Update contracts/libraries/Math.sol Co-authored-by: Duc Tho Tran * Add comments Co-authored-by: nxqbao --- .../collections/HasSlashIndicatorContract.sol | 2 +- contracts/interfaces/ICandidateManager.sol | 5 + contracts/interfaces/IMaintenance.sol | 18 + contracts/interfaces/IRoninValidatorSet.sol | 49 ++- .../{ => slash-indicator}/IBaseSlash.sol | 0 .../slash-indicator/ICreditScore.sol | 82 ++++ .../ISlashBridgeOperator.sol | 0 .../ISlashBridgeVoting.sol | 0 .../ISlashDoubleSign.sol | 0 .../{ => slash-indicator}/ISlashIndicator.sol | 9 +- .../ISlashUnavailability.sol | 0 contracts/libraries/Math.sol | 30 ++ contracts/mocks/MockValidatorSet.sol | 34 +- contracts/ronin/Maintenance.sol | 41 +- .../ronin/slash-indicator/CreditScore.sol | 196 +++++++++ .../slash-indicator/SlashBridgeOperator.sol | 4 +- .../slash-indicator/SlashBridgeVoting.sol | 4 +- .../ronin/slash-indicator/SlashDoubleSign.sol | 2 +- .../ronin/slash-indicator/SlashIndicator.sol | 36 +- .../slash-indicator/SlashUnavailability.sol | 22 +- .../ronin/validator/RoninValidatorSet.sol | 88 +++- src/deploy/proxy/slash-indicator-proxy.ts | 5 + src/script/slash-indicator.ts | 7 +- src/utils.ts | 7 + test/helpers/fixture.ts | 9 + test/helpers/ronin-validator-set.ts | 10 +- test/helpers/slash.ts | 66 +++ .../integration/ActionSlashValidators.test.ts | 8 +- test/slash/CreditScore.test.ts | 410 ++++++++++++++++++ test/slash/SlashIndicator.test.ts | 52 +-- 30 files changed, 1136 insertions(+), 60 deletions(-) rename contracts/interfaces/{ => slash-indicator}/IBaseSlash.sol (100%) create mode 100644 contracts/interfaces/slash-indicator/ICreditScore.sol rename contracts/interfaces/{ => slash-indicator}/ISlashBridgeOperator.sol (100%) rename contracts/interfaces/{ => slash-indicator}/ISlashBridgeVoting.sol (100%) rename contracts/interfaces/{ => slash-indicator}/ISlashDoubleSign.sol (100%) rename contracts/interfaces/{ => slash-indicator}/ISlashIndicator.sol (54%) rename contracts/interfaces/{ => slash-indicator}/ISlashUnavailability.sol (100%) create mode 100644 contracts/ronin/slash-indicator/CreditScore.sol create mode 100644 test/helpers/slash.ts create mode 100644 test/slash/CreditScore.test.ts diff --git a/contracts/extensions/collections/HasSlashIndicatorContract.sol b/contracts/extensions/collections/HasSlashIndicatorContract.sol index 8b352ea9d..328ce44e3 100644 --- a/contracts/extensions/collections/HasSlashIndicatorContract.sol +++ b/contracts/extensions/collections/HasSlashIndicatorContract.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import "./HasProxyAdmin.sol"; import "../../interfaces/collections/IHasSlashIndicatorContract.sol"; -import "../../interfaces/ISlashIndicator.sol"; +import "../../interfaces/slash-indicator/ISlashIndicator.sol"; contract HasSlashIndicatorContract is IHasSlashIndicatorContract, HasProxyAdmin { ISlashIndicator internal _slashIndicatorContract; diff --git a/contracts/interfaces/ICandidateManager.sol b/contracts/interfaces/ICandidateManager.sol index 7769683cd..ac1cd98bf 100644 --- a/contracts/interfaces/ICandidateManager.sol +++ b/contracts/interfaces/ICandidateManager.sol @@ -109,6 +109,11 @@ interface ICandidateManager { */ function currentPeriod() external view returns (uint256); + /** + * @dev Returns the block number that the current period starts at. + */ + function currentPeriodStartAtBlock() external view returns (uint256); + /** * @dev Returns the number of blocks in a epoch. */ diff --git a/contracts/interfaces/IMaintenance.sol b/contracts/interfaces/IMaintenance.sol index 7f67515d1..99b7c0946 100644 --- a/contracts/interfaces/IMaintenance.sol +++ b/contracts/interfaces/IMaintenance.sol @@ -24,11 +24,29 @@ interface IMaintenance { */ function maintaining(address _consensusAddr, uint256 _block) external view returns (bool); + /** + * @dev Returns whether the validator `_consensusAddr` was maintaining in the inclusive range [`_fromBlock`, `_toBlock`] of blocks. + */ + function maintainingInBlockRange( + address _consensusAddr, + uint256 _fromBlock, + uint256 _toBlock + ) external view returns (bool); + /** * @dev Returns the bool array indicating the validator is maintaining or not. */ function bulkMaintaining(address[] calldata _addrList, uint256 _block) external view returns (bool[] memory); + /** + * @dev Returns a bool array indicating the validator was maintaining in the inclusive range [`_fromBlock`, `_toBlock`] of blocks or not. + */ + function bulkMaintainingInBlockRange( + address[] calldata _addrList, + uint256 _fromBlock, + uint256 _toBlock + ) external view returns (bool[] memory); + /** * @dev Returns whether the validator `_consensusAddr` has scheduled. */ diff --git a/contracts/interfaces/IRoninValidatorSet.sol b/contracts/interfaces/IRoninValidatorSet.sol index 5a34969c3..1a8ccc3ff 100644 --- a/contracts/interfaces/IRoninValidatorSet.sol +++ b/contracts/interfaces/IRoninValidatorSet.sol @@ -27,6 +27,8 @@ interface IRoninValidatorSet is ICandidateManager { bool blockProducerRewardDeprecated, bool bridgeOperatorRewardDeprecated ); + /// @dev Emitted when the validator get out of jail by bailout. + event ValidatorLiberated(address indexed validator); /// @dev Emitted when the reward of the block producer is deprecated. event BlockRewardRewardDeprecated(address indexed coinbaseAddr, uint256 rewardAmount); /// @dev Emitted when the block reward is submitted. @@ -124,10 +126,55 @@ interface IRoninValidatorSet is ICandidateManager { uint256 _slashAmount ) external; + /** + * @dev Bailout the validator. + * + * Requirements: + * - The method caller is slash indicator contract. + * + * Emits the event `ValidatorLiberated`. + * + */ + function bailOut(address _validatorAddr) external; + + /** + * @dev Returns whether the validator are put in jail (cannot join the set of validators) during the current period. + */ + function jailed(address) external view returns (bool); + + /** + * @dev Returns whether the validator are put in jail and the number of block and epoch that he still is in the jail. + */ + function jailedTimeLeft(address _addr) + external + view + returns ( + bool isJailed_, + uint256 blockLeft_, + uint256 epochLeft_ + ); + + /** + * @dev Returns whether the validator are put in jail (cannot join the set of validators) at a specific block. + */ + function jailedAtBlock(address _addr, uint256 _blockNum) external view returns (bool); + + /** + * @dev Returns whether the validator are put in jail at a specific block and the number of block and epoch that he still is in the jail. + */ + function jailedTimeLeftAtBlock(address _addr, uint256 _blockNum) + external + view + returns ( + bool isJailed_, + uint256 blockLeft_, + uint256 epochLeft_ + ); + /** * @dev Returns whether the validators are put in jail (cannot join the set of validators) during the current period. */ - function jailed(address[] memory) external view returns (bool[] memory); + function bulkJailed(address[] memory) external view returns (bool[] memory); /** * @dev Returns whether the incoming reward of the block producers are deprecated during the current period. diff --git a/contracts/interfaces/IBaseSlash.sol b/contracts/interfaces/slash-indicator/IBaseSlash.sol similarity index 100% rename from contracts/interfaces/IBaseSlash.sol rename to contracts/interfaces/slash-indicator/IBaseSlash.sol diff --git a/contracts/interfaces/slash-indicator/ICreditScore.sol b/contracts/interfaces/slash-indicator/ICreditScore.sol new file mode 100644 index 000000000..ea8857bac --- /dev/null +++ b/contracts/interfaces/slash-indicator/ICreditScore.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface ICreditScore { + /// @dev Emitted when the configs to credit score is updated. See the method `setCreditScoreConfigs` for param details. + event CreditScoreConfigsUpdated(uint256 gainCreditScore, uint256 maxCreditScore, uint256 bailOutCostMultiplier); + /// @dev Emitted the credit score of validators is updated + event CreditScoresUpdated(address[] validators, uint256[] creditScores); + /// @dev Emitted when a validator bailed out of jail + event BailedOut(address indexed validator, uint256 period); + + /** + * @dev Updates the credit score for the validators. + * + * Requirements: + * - Only validator contract can call this method. + * - This method is only called at the end of each period. + * + * Emits the event `CreditScoresUpdated`. + * + */ + function updateCreditScore(address[] calldata _validators, uint256 _period) external; + + /** + * @dev A slashed validator use this method to get out of jail. + * + * Requirements: + * - The `_consensusAddr` must be a validator. + * - Only validator's admin can call this method. + * + * Emits the event `BailedOut`. + * + */ + function bailOut(address _consensusAddr) external; + + /** + * @dev Sets the configs to credit score. + * + * Requirements: + * - The method caller is admin. + * + * Emits the event `CreditScoreConfigsUpdated`. + * + * @param _gainCreditScore The max gained number of credit score per period. + * @param _maxCreditScore The max number of credit score that a validator can hold. + * @param _bailOutCostMultiplier The number that will be multiplied with the remaining jailed time to get the cost of bailing out. + * + */ + function setCreditScoreConfigs( + uint256 _gainCreditScore, + uint256 _maxCreditScore, + uint256 _bailOutCostMultiplier + ) external; + + /** + * @dev Returns the configs related to credit score. + * + * @return _gainCreditScore The max gained number of credit score per period. + * @return _maxCreditScore The max number of credit score that a validator can hold. + * @return _bailOutCostMultiplier The number that will be multiplied with the remaining jailed time to get the cost of bailing out. + * + */ + function getCreditScoreConfigs() + external + view + returns ( + uint256 _gainCreditScore, + uint256 _maxCreditScore, + uint256 _bailOutCostMultiplier + ); + + /** + * @dev Returns the current credit score of the validator. + */ + function getCreditScore(address _validator) external view returns (uint256); + + /** + * @dev Returns the current credit score of a list of validators. + */ + function getBulkCreditScore(address[] calldata _validators) external view returns (uint256[] memory _resultList); +} diff --git a/contracts/interfaces/ISlashBridgeOperator.sol b/contracts/interfaces/slash-indicator/ISlashBridgeOperator.sol similarity index 100% rename from contracts/interfaces/ISlashBridgeOperator.sol rename to contracts/interfaces/slash-indicator/ISlashBridgeOperator.sol diff --git a/contracts/interfaces/ISlashBridgeVoting.sol b/contracts/interfaces/slash-indicator/ISlashBridgeVoting.sol similarity index 100% rename from contracts/interfaces/ISlashBridgeVoting.sol rename to contracts/interfaces/slash-indicator/ISlashBridgeVoting.sol diff --git a/contracts/interfaces/ISlashDoubleSign.sol b/contracts/interfaces/slash-indicator/ISlashDoubleSign.sol similarity index 100% rename from contracts/interfaces/ISlashDoubleSign.sol rename to contracts/interfaces/slash-indicator/ISlashDoubleSign.sol diff --git a/contracts/interfaces/ISlashIndicator.sol b/contracts/interfaces/slash-indicator/ISlashIndicator.sol similarity index 54% rename from contracts/interfaces/ISlashIndicator.sol rename to contracts/interfaces/slash-indicator/ISlashIndicator.sol index 8cfaf831f..18a62dd81 100644 --- a/contracts/interfaces/ISlashIndicator.sol +++ b/contracts/interfaces/slash-indicator/ISlashIndicator.sol @@ -6,5 +6,12 @@ import "./ISlashDoubleSign.sol"; import "./ISlashBridgeVoting.sol"; import "./ISlashBridgeOperator.sol"; import "./ISlashUnavailability.sol"; +import "./ICreditScore.sol"; -interface ISlashIndicator is ISlashDoubleSign, ISlashBridgeVoting, ISlashBridgeOperator, ISlashUnavailability {} +interface ISlashIndicator is + ISlashDoubleSign, + ISlashBridgeVoting, + ISlashBridgeOperator, + ISlashUnavailability, + ICreditScore +{} diff --git a/contracts/interfaces/ISlashUnavailability.sol b/contracts/interfaces/slash-indicator/ISlashUnavailability.sol similarity index 100% rename from contracts/interfaces/ISlashUnavailability.sol rename to contracts/interfaces/slash-indicator/ISlashUnavailability.sol diff --git a/contracts/libraries/Math.sol b/contracts/libraries/Math.sol index 754320a45..0c6d423e6 100644 --- a/contracts/libraries/Math.sol +++ b/contracts/libraries/Math.sol @@ -27,4 +27,34 @@ library Math { ) internal pure returns (bool) { return a <= c && c <= b; } + + /** + * @dev Returns whether two inclusive ranges [x1;x2] and [y1;y2] overlap. + */ + function twoRangeOverlap( + uint256 x1, + uint256 x2, + uint256 y1, + uint256 y2 + ) internal pure returns (bool) { + return x1 <= y2 && y1 <= x2; + } + + /** + * @dev Returns value of a + b; in case result is larger than upperbound, upperbound is returned. + */ + function addWithUpperbound( + uint256 a, + uint256 b, + uint256 upperbound + ) internal pure returns (uint256) { + return min(a + b, upperbound); + } + + /** + * @dev Returns value of a - b; in case of negative result, 0 is returned. + */ + function subNonNegative(uint256 a, uint256 b) internal pure returns (uint256) { + return a - min(a, b); + } } diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index af6304935..8b30f07c5 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.9; -import "../interfaces/ISlashIndicator.sol"; +import "../interfaces/slash-indicator/ISlashIndicator.sol"; import "../interfaces/IRoninValidatorSet.sol"; import "../interfaces/IStaking.sol"; import "../ronin/validator/CandidateManager.sol"; @@ -60,7 +60,7 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { function getLastUpdatedBlock() external view override returns (uint256) {} - function jailed(address[] memory) external view override returns (bool[] memory) {} + function bulkJailed(address[] memory) external view override returns (bool[] memory) {} function miningRewardDeprecatedAtPeriod(address[] memory, uint256 _period) external @@ -83,6 +83,8 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { uint256 _slashAmount ) external override {} + function bailOut(address) external override {} + function setMaxValidatorNumber(uint256 _maxValidatorNumber) external override {} function setNumberOfBlocksInEpoch(uint256 _number) external override {} @@ -127,4 +129,32 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { function currentPeriod() public view override(CandidateManager, ICandidateManager) returns (uint256) { return block.timestamp / 86400; } + + function jailed(address) external view override returns (bool) {} + + function jailedTimeLeft(address) + external + view + override + returns ( + bool, + uint256, + uint256 + ) + {} + + function currentPeriodStartAtBlock() external view override returns (uint256) {} + + function jailedAtBlock(address _addr, uint256 _blockNum) external view override returns (bool) {} + + function jailedTimeLeftAtBlock(address _addr, uint256 _blockNum) + external + view + override + returns ( + bool isJailed_, + uint256 blockLeft_, + uint256 epochLeft_ + ) + {} } diff --git a/contracts/ronin/Maintenance.sol b/contracts/ronin/Maintenance.sol index 76ea696c3..11f4d42c1 100644 --- a/contracts/ronin/Maintenance.sol +++ b/contracts/ronin/Maintenance.sol @@ -113,7 +113,21 @@ contract Maintenance is IMaintenance, HasValidatorContract, Initializable { /** * @inheritdoc IMaintenance */ - function totalSchedules() public view returns (uint256 _count) { + function bulkMaintainingInBlockRange( + address[] calldata _addrList, + uint256 _fromBlock, + uint256 _toBlock + ) external view override returns (bool[] memory _resList) { + _resList = new bool[](_addrList.length); + for (uint _i = 0; _i < _addrList.length; _i++) { + _resList[_i] = _maintainingInBlockRange(_addrList[_i], _fromBlock, _toBlock); + } + } + + /** + * @inheritdoc IMaintenance + */ + function totalSchedules() public view override returns (uint256 _count) { address[] memory _validators = _validatorContract.getValidators(); for (uint _i = 0; _i < _validators.length; _i++) { if (scheduled(_validators[_i])) { @@ -130,6 +144,17 @@ contract Maintenance is IMaintenance, HasValidatorContract, Initializable { return _s.from <= _block && _block <= _s.to; } + /** + * @inheritdoc IMaintenance + */ + function maintainingInBlockRange( + address _consensusAddr, + uint256 _fromBlock, + uint256 _toBlock + ) public view override returns (bool) { + return _maintainingInBlockRange(_consensusAddr, _fromBlock, _toBlock); + } + /** * @inheritdoc IMaintenance */ @@ -159,4 +184,18 @@ contract Maintenance is IMaintenance, HasValidatorContract, Initializable { maxSchedules = _maxSchedules; emit MaintenanceConfigUpdated(_minMaintenanceBlockPeriod, _maxMaintenanceBlockPeriod, _minOffset, _maxSchedules); } + + /** + * @dev Check if the validator was maintaining in the current period. + * + * Note: This method should be called at the end of the period. + */ + function _maintainingInBlockRange( + address _consensusAddr, + uint256 _fromBlock, + uint256 _toBlock + ) private view returns (bool) { + Schedule storage _s = _schedule[_consensusAddr]; + return Math.twoRangeOverlap(_fromBlock, _toBlock, _s.from, _s.to); + } } diff --git a/contracts/ronin/slash-indicator/CreditScore.sol b/contracts/ronin/slash-indicator/CreditScore.sol new file mode 100644 index 000000000..ec74cb4a3 --- /dev/null +++ b/contracts/ronin/slash-indicator/CreditScore.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +import "../../interfaces/slash-indicator/ICreditScore.sol"; +import "../../extensions/collections/HasMaintenanceContract.sol"; +import "../../extensions/collections/HasValidatorContract.sol"; +import "../../libraries/Math.sol"; + +abstract contract CreditScore is ICreditScore, HasValidatorContract, HasMaintenanceContract { + /// @dev Mapping from validator address => period index => whether bailed out before + mapping(address => mapping(uint256 => bool)) internal _bailedOutStatus; + /// @dev Mapping from validator address => credit score + mapping(address => uint256) internal _creditScore; + + /// @dev The max gained number of credit score per period. + uint256 public gainCreditScore; + /// @dev The max number of credit score that a validator can hold. + uint256 public maxCreditScore; + /// @dev The number that will be multiplied with the remaining jailed time to get the cost of bailing out. + uint256 public bailOutCostMultiplier; + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + */ + uint256[50] private ______gap; + + /////////////////////////////////////////////////////////////////////////////////////// + // CREDIT SCORE FUNCTIONS // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @inheritdoc ICreditScore + */ + function updateCreditScore(address[] calldata _validators, uint256 _period) external override onlyValidatorContract { + uint256 _periodStartAtBlock = _validatorContract.currentPeriodStartAtBlock(); + + bool[] memory _jaileds = _validatorContract.bulkJailed(_validators); + bool[] memory _maintaineds = _maintenanceContract.bulkMaintainingInBlockRange( + _validators, + _periodStartAtBlock, + block.number + ); + uint256[] memory _updatedCreditScores = new uint256[](_validators.length); + + for (uint _i = 0; _i < _validators.length; _i++) { + address _validator = _validators[_i]; + + uint256 _indicator = getUnavailabilityIndicator(_validator, _period); + bool _isJailedInPeriod = _jaileds[_i]; + bool _isMaintainingInPeriod = _maintaineds[_i]; + + uint256 _actualGain = (_isJailedInPeriod || _isMaintainingInPeriod) + ? 0 + : Math.subNonNegative(gainCreditScore, _indicator); + uint256 _scoreBeforeGain = _creditScore[_validator]; + uint256 _scoreAfterGain = Math.addWithUpperbound(_creditScore[_validator], _actualGain, maxCreditScore); + + if (_scoreBeforeGain != _scoreAfterGain) { + _creditScore[_validator] = _scoreAfterGain; + } + + _updatedCreditScores[_i] = _creditScore[_validator]; + } + + emit CreditScoresUpdated(_validators, _updatedCreditScores); + } + + /** + * @inheritdoc ICreditScore + */ + function bailOut(address _consensusAddr) external override { + require( + _validatorContract.isValidatorCandidate(_consensusAddr), + "SlashIndicator: consensus address must be a validator candidate" + ); + require( + _validatorContract.isCandidateAdmin(_consensusAddr, msg.sender), + "SlashIndicator: method caller must be a candidate admin" + ); + + (bool _isJailed, , uint256 _jailedEpochLeft) = _validatorContract.jailedTimeLeft(_consensusAddr); + require(_isJailed, "SlashIndicator: caller must be jailed in the current period"); + + uint256 _period = _validatorContract.currentPeriod(); + require(!_bailedOutStatus[_consensusAddr][_period], "SlashIndicator: validator has bailed out previously"); + + uint256 _score = _creditScore[_consensusAddr]; + uint256 _cost = _jailedEpochLeft * bailOutCostMultiplier; + require(_score >= _cost, "SlashIndicator: insufficient credit score to bail out"); + + _validatorContract.bailOut(_consensusAddr); + + _creditScore[_consensusAddr] -= _cost; + _setUnavailabilityIndicator(_consensusAddr, _period, 0); + _bailedOutStatus[_consensusAddr][_period] = true; + + // TODO: - Remove all rewards of the validator before the bailout + // TODO: - After the bailout, the validator gets 50% of the rewards until the end of the period. + } + + /////////////////////////////////////////////////////////////////////////////////////// + // GOVERNANCE FUNCTIONS // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @inheritdoc ICreditScore + */ + function setCreditScoreConfigs( + uint256 _gainCreditScore, + uint256 _maxCreditScore, + uint256 _bailOutCostMultiplier + ) external override onlyAdmin { + _setCreditScoreConfigs(_gainCreditScore, _maxCreditScore, _bailOutCostMultiplier); + } + + /////////////////////////////////////////////////////////////////////////////////////// + // QUERY FUNCTIONS // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @dev See `ISlashUnavailability` + */ + function getUnavailabilityIndicator(address _validator, uint256 _period) public view virtual returns (uint256); + + /** + * @inheritdoc ICreditScore + */ + function getCreditScoreConfigs() + external + view + override + returns ( + uint256 _gainCreditScore, + uint256 _maxCreditScore, + uint256 _bailOutCostMultiplier + ) + { + _gainCreditScore = gainCreditScore; + _maxCreditScore = maxCreditScore; + _bailOutCostMultiplier = bailOutCostMultiplier; + } + + /** + * @inheritdoc ICreditScore + */ + function getCreditScore(address _validator) external view override returns (uint256) { + return _creditScore[_validator]; + } + + /** + * @inheritdoc ICreditScore + */ + function getBulkCreditScore(address[] calldata _validators) + public + view + override + returns (uint256[] memory _resultList) + { + _resultList = new uint256[](_validators.length); + + for (uint _i = 0; _i < _resultList.length; _i++) { + _resultList[_i] = _creditScore[_validators[_i]]; + } + } + + /////////////////////////////////////////////////////////////////////////////////////// + // HELPER FUNCTIONS // + /////////////////////////////////////////////////////////////////////////////////////// + + /** + * @dev See `ISlashUnavailability` + */ + function _setUnavailabilityIndicator( + address _validator, + uint256 _period, + uint256 _indicator + ) internal virtual; + + /** + * @dev See `ICreditScore-CreditScoreConfigsUpdated`. + */ + function _setCreditScoreConfigs( + uint256 _gainCreditScore, + uint256 _maxCreditScore, + uint256 _bailOutCostMultiplier + ) internal { + require(_gainCreditScore <= _maxCreditScore, "CreditScore: invalid credit score config"); + + gainCreditScore = _gainCreditScore; + maxCreditScore = _maxCreditScore; + bailOutCostMultiplier = _bailOutCostMultiplier; + emit CreditScoreConfigsUpdated(_gainCreditScore, _maxCreditScore, _bailOutCostMultiplier); + } +} diff --git a/contracts/ronin/slash-indicator/SlashBridgeOperator.sol b/contracts/ronin/slash-indicator/SlashBridgeOperator.sol index 04c4a190f..dd143d11b 100644 --- a/contracts/ronin/slash-indicator/SlashBridgeOperator.sol +++ b/contracts/ronin/slash-indicator/SlashBridgeOperator.sol @@ -4,9 +4,9 @@ pragma solidity ^0.8.9; import "../../extensions/consumers/PercentageConsumer.sol"; import "../../extensions/collections/HasProxyAdmin.sol"; -import "../../interfaces/ISlashBridgeOperator.sol"; +import "../../interfaces/slash-indicator/ISlashBridgeOperator.sol"; -contract SlashBridgeOperator is ISlashBridgeOperator, HasProxyAdmin, PercentageConsumer { +abstract contract SlashBridgeOperator is ISlashBridgeOperator, HasProxyAdmin, PercentageConsumer { /** * @dev The bridge operators will be deprecated reward if (s)he missed more than the ratio. * Values 0-10,000 map to 0%-100%. diff --git a/contracts/ronin/slash-indicator/SlashBridgeVoting.sol b/contracts/ronin/slash-indicator/SlashBridgeVoting.sol index 79e1198be..59af273dc 100644 --- a/contracts/ronin/slash-indicator/SlashBridgeVoting.sol +++ b/contracts/ronin/slash-indicator/SlashBridgeVoting.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.9; import "../../libraries/Math.sol"; -import "../../interfaces/ISlashBridgeVoting.sol"; +import "../../interfaces/slash-indicator/ISlashBridgeVoting.sol"; import "../../extensions/collections/HasRoninTrustedOrganizationContract.sol"; import "../../extensions/collections/HasRoninGovernanceAdminContract.sol"; import "../../extensions/collections/HasValidatorContract.sol"; -contract SlashBridgeVoting is +abstract contract SlashBridgeVoting is ISlashBridgeVoting, HasValidatorContract, HasRoninTrustedOrganizationContract, diff --git a/contracts/ronin/slash-indicator/SlashDoubleSign.sol b/contracts/ronin/slash-indicator/SlashDoubleSign.sol index cf523834b..6b4c056ad 100644 --- a/contracts/ronin/slash-indicator/SlashDoubleSign.sol +++ b/contracts/ronin/slash-indicator/SlashDoubleSign.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.9; -import "../../interfaces/ISlashDoubleSign.sol"; +import "../../interfaces/slash-indicator/ISlashDoubleSign.sol"; import "../../precompile-usages/PrecompileUsageValidateDoubleSign.sol"; import "../../extensions/collections/HasValidatorContract.sol"; diff --git a/contracts/ronin/slash-indicator/SlashIndicator.sol b/contracts/ronin/slash-indicator/SlashIndicator.sol index e924f725e..09deda6b4 100644 --- a/contracts/ronin/slash-indicator/SlashIndicator.sol +++ b/contracts/ronin/slash-indicator/SlashIndicator.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "../../interfaces/ISlashIndicator.sol"; -import "../../extensions/collections/HasMaintenanceContract.sol"; +import "../../interfaces/slash-indicator/ISlashIndicator.sol"; import "./SlashDoubleSign.sol"; import "./SlashBridgeVoting.sol"; import "./SlashBridgeOperator.sol"; import "./SlashUnavailability.sol"; +import "./CreditScore.sol"; contract SlashIndicator is ISlashIndicator, @@ -16,7 +16,7 @@ contract SlashIndicator is SlashBridgeVoting, SlashBridgeOperator, SlashUnavailability, - HasMaintenanceContract, + CreditScore, Initializable { constructor() { @@ -45,7 +45,11 @@ contract SlashIndicator is // _unavailabilitySlashingConfigs[1]: _unavailabilityTier2Threshold // _unavailabilitySlashingConfigs[2]: _slashAmountForUnavailabilityTier2Threshold // _unavailabilitySlashingConfigs[3]: _jailDurationForUnavailabilityTier2Threshold - uint256[4] calldata _unavailabilitySlashingConfigs + uint256[4] calldata _unavailabilitySlashingConfigs, + // _creditScoreConfigs[0]: _gainCreditScore + // _creditScoreConfigs[1]: _maxCreditScore + // _creditScoreConfigs[2]: _bailOutCostMultiplier + uint256[3] calldata _creditScoreConfigs ) external initializer { _setValidatorContract(__validatorContract); _setMaintenanceContract(__maintenanceContract); @@ -64,6 +68,30 @@ contract SlashIndicator is _unavailabilitySlashingConfigs[2], _unavailabilitySlashingConfigs[3] ); + _setCreditScoreConfigs(_creditScoreConfigs[0], _creditScoreConfigs[1], _creditScoreConfigs[2]); + } + + /** + * @dev Helper for CreditScore contract to reset the indicator of the validator after bailing out. + */ + function _setUnavailabilityIndicator( + address _validator, + uint256 _period, + uint256 _indicator + ) internal override(CreditScore, SlashUnavailability) { + SlashUnavailability._setUnavailabilityIndicator(_validator, _period, _indicator); + } + + /** + * @dev Helper for CreditScore contract to query indicator of the validator. + */ + function getUnavailabilityIndicator(address _validator, uint256 _period) + public + view + override(CreditScore, ISlashUnavailability, SlashUnavailability) + returns (uint256) + { + return SlashUnavailability.getUnavailabilityIndicator(_validator, _period); } /** diff --git a/contracts/ronin/slash-indicator/SlashUnavailability.sol b/contracts/ronin/slash-indicator/SlashUnavailability.sol index 7f20ab15e..3c5acaadc 100644 --- a/contracts/ronin/slash-indicator/SlashUnavailability.sol +++ b/contracts/ronin/slash-indicator/SlashUnavailability.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.9; -import "../../interfaces/ISlashUnavailability.sol"; +import "./CreditScore.sol"; +import "../../interfaces/slash-indicator/ISlashUnavailability.sol"; import "../../extensions/collections/HasValidatorContract.sol"; abstract contract SlashUnavailability is ISlashUnavailability, HasValidatorContract { @@ -112,10 +113,27 @@ abstract contract SlashUnavailability is ISlashUnavailability, HasValidatorContr /** * @inheritdoc ISlashUnavailability */ - function getUnavailabilityIndicator(address _validator, uint256 _period) public view override returns (uint256) { + function getUnavailabilityIndicator(address _validator, uint256 _period) + public + view + virtual + override + returns (uint256) + { return _unavailabilityIndicator[_validator][_period]; } + /** + * @dev Sets the unavailability indicator of the `_validator` at `_period`. + */ + function _setUnavailabilityIndicator( + address _validator, + uint256 _period, + uint256 _indicator + ) internal virtual { + _unavailabilityIndicator[_validator][_period] = _indicator; + } + /** * @dev See `ISlashUnavailability-setUnavailabilitySlashingConfigs`. */ diff --git a/contracts/ronin/validator/RoninValidatorSet.sol b/contracts/ronin/validator/RoninValidatorSet.sol index 5efe08744..8ca765989 100644 --- a/contracts/ronin/validator/RoninValidatorSet.sol +++ b/contracts/ronin/validator/RoninValidatorSet.sol @@ -43,6 +43,8 @@ contract RoninValidatorSet is uint256 internal _lastUpdatedBlock; /// @dev The last updated period uint256 internal _lastUpdatedPeriod; + /// @dev The starting block of the last updated period + uint256 internal _currentPeriodStartAtBlock; /// @dev The total of validators uint256 public validatorCount; @@ -171,6 +173,7 @@ contract RoninValidatorSet is function wrapUpEpoch() external payable virtual override onlyCoinbase whenEpochEnding oncePerEpoch { uint256 _newPeriod = _computePeriod(block.timestamp); bool _periodEnding = _isPeriodEnding(_newPeriod); + _currentPeriodStartAtBlock = block.number + 1; address[] memory _currentValidators = getValidators(); uint256 _epoch = epochOf(block.number); @@ -182,6 +185,7 @@ contract RoninValidatorSet is _currentValidators ); _settleAndTransferDelegatingRewards(_currentValidators, _totalDelegatingReward); + _slashIndicatorContract.updateCreditScore(_currentValidators, _lastPeriod); _currentValidators = _syncValidatorSet(_newPeriod); } @@ -201,7 +205,7 @@ contract RoninValidatorSet is address _validatorAddr, uint256 _newJailedUntil, uint256 _slashAmount - ) external onlySlashIndicatorContract { + ) external override onlySlashIndicatorContract { uint256 _period = currentPeriod(); _miningRewardDeprecatedAtPeriod[_validatorAddr][_period] = true; delete _miningReward[_validatorAddr]; @@ -222,7 +226,69 @@ contract RoninValidatorSet is /** * @inheritdoc IRoninValidatorSet */ - function jailed(address[] memory _addrList) external view override returns (bool[] memory _result) { + function bailOut(address _validatorAddr) external override onlySlashIndicatorContract { + _jailedUntil[_validatorAddr] = block.number - 1; + + emit ValidatorLiberated(_validatorAddr); + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function jailed(address _addr) external view override returns (bool) { + return jailedAtBlock(_addr, block.number); + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function jailedTimeLeft(address _addr) + external + view + override + returns ( + bool isJailed_, + uint256 blockLeft_, + uint256 epochLeft_ + ) + { + return jailedTimeLeftAtBlock(_addr, block.number); + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function jailedAtBlock(address _addr, uint256 _blockNum) public view override returns (bool) { + return _jailedAtBlock(_addr, _blockNum); + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function jailedTimeLeftAtBlock(address _addr, uint256 _blockNum) + public + view + override + returns ( + bool isJailed_, + uint256 blockLeft_, + uint256 epochLeft_ + ) + { + uint256 __jailedUntil = _jailedUntil[_addr]; + if (__jailedUntil < _blockNum) { + return (false, 0, 0); + } + + isJailed_ = true; + blockLeft_ = __jailedUntil - _blockNum + 1; + epochLeft_ = epochOf(__jailedUntil) - epochOf(_blockNum) + 1; + } + + /** + * @inheritdoc IRoninValidatorSet + */ + function bulkJailed(address[] memory _addrList) external view override returns (bool[] memory _result) { _result = new bool[](_addrList.length); for (uint256 _i; _i < _addrList.length; _i++) { _result[_i] = _jailed(_addrList[_i]); @@ -278,10 +344,17 @@ contract RoninValidatorSet is return _lastUpdatedPeriod; } + /** + * @inheritdoc ICandidateManager + */ + function currentPeriodStartAtBlock() public view virtual override returns (uint256) { + return _currentPeriodStartAtBlock; + } + /** * @inheritdoc IRoninValidatorSet */ - function getLastUpdatedBlock() external view returns (uint256) { + function getLastUpdatedBlock() external view override returns (uint256) { return _lastUpdatedBlock; } @@ -705,7 +778,14 @@ contract RoninValidatorSet is * @dev Returns whether the reward of the validator is put in jail (cannot join the set of validators) during the current period. */ function _jailed(address _validatorAddr) internal view returns (bool) { - return block.number <= _jailedUntil[_validatorAddr]; + return _jailedAtBlock(_validatorAddr, block.number); + } + + /** + * @dev Returns whether the reward of the validator is put in jail (cannot join the set of validators) at a specific block. + */ + function _jailedAtBlock(address _validatorAddr, uint256 _blockNum) internal view returns (bool) { + return _blockNum <= _jailedUntil[_validatorAddr]; } /** diff --git a/src/deploy/proxy/slash-indicator-proxy.ts b/src/deploy/proxy/slash-indicator-proxy.ts index fc508d9b6..644f6337e 100644 --- a/src/deploy/proxy/slash-indicator-proxy.ts +++ b/src/deploy/proxy/slash-indicator-proxy.ts @@ -39,6 +39,11 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme slashIndicatorConf[network.name]!.unavailabilitySlashing?.slashAmountForUnavailabilityTier2Threshold, slashIndicatorConf[network.name]!.unavailabilitySlashing?.jailDurationForUnavailabilityTier2Threshold, ], + [ + slashIndicatorConf[network.name]!.creditScore?.gainCreditScore, + slashIndicatorConf[network.name]!.creditScore?.maxCreditScore, + slashIndicatorConf[network.name]!.creditScore?.bailOutCostMultiplier, + ], ]); const deployment = await deploy('SlashIndicatorProxy', { diff --git a/src/script/slash-indicator.ts b/src/script/slash-indicator.ts index bed174ea9..fbd1eb58c 100644 --- a/src/script/slash-indicator.ts +++ b/src/script/slash-indicator.ts @@ -1,6 +1,9 @@ export enum SlashType { UNKNOWN = 0, - MISDEMEANOR = 1, - FELONY = 2, + UNAVAILABILITY_TIER_1 = 1, + UNAVAILABILITY_TIER_2 = 2, DOUBLE_SIGNING = 3, + BRIDGE_VOTING = 4, + BRIDGE_OPERATOR_MISSING_VOTE_TIER_1 = 5, + BRIDGE_OPERATOR_MISSING_VOTE_TIER_2 = 6, } diff --git a/src/utils.ts b/src/utils.ts index ce92af82e..ef28db3f0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -111,11 +111,18 @@ export interface UnavailabilitySlashing { jailDurationForUnavailabilityTier2Threshold?: BigNumberish; } +export interface CreditScoreConfig { + gainCreditScore?: BigNumberish; + maxCreditScore?: BigNumberish; + bailOutCostMultiplier?: BigNumberish; +} + export interface SlashIndicatorArguments { bridgeOperatorSlashing?: BridgeOperatorSlashingConfig; bridgeVotingSlashing?: BridgeVotingSlashingConfig; doubleSignSlashing?: DoubleSignSlashingConfig; unavailabilitySlashing?: UnavailabilitySlashing; + creditScore?: CreditScoreConfig; } export interface SlashIndicatorConfig { diff --git a/test/helpers/fixture.ts b/test/helpers/fixture.ts index bb7e4044e..f3dacb7b9 100644 --- a/test/helpers/fixture.ts +++ b/test/helpers/fixture.ts @@ -91,6 +91,11 @@ export const defaultTestConfig: InitTestInput = { slashAmountForUnavailabilityTier2Threshold: BigNumber.from(10).pow(18).mul(1), jailDurationForUnavailabilityTier2Threshold: 28800 * 2, }, + creditScore: { + gainCreditScore: 50, + maxCreditScore: 600, + bailOutCostMultiplier: 5, + }, }, roninValidatorSetArguments: { @@ -146,6 +151,10 @@ export const initTest = (id: string) => ...defaultTestConfig?.slashIndicatorArguments?.unavailabilitySlashing, ...options?.slashIndicatorArguments?.unavailabilitySlashing, }, + creditScore: { + ...defaultTestConfig?.slashIndicatorArguments?.creditScore, + ...options?.slashIndicatorArguments?.creditScore, + }, }; roninValidatorSetConf[network.name] = { ...defaultTestConfig?.roninValidatorSetArguments, diff --git a/test/helpers/ronin-validator-set.ts b/test/helpers/ronin-validator-set.ts index 3cf6e25f0..1a448d82e 100644 --- a/test/helpers/ronin-validator-set.ts +++ b/test/helpers/ronin-validator-set.ts @@ -31,12 +31,18 @@ export class EpochController { return BigNumber.from(block).add(this.diffToEndEpoch(block)); } - async mineToBeforeEndOfEpoch() { + async mineToBeforeEndOfEpoch(includingEpochsNum?: BigNumberish) { let number = this.diffToEndEpoch(await ethers.provider.getBlockNumber()).sub(1); if (number.lt(0)) { number = number.add(this.numberOfBlocksInEpoch); } - return network.provider.send('hardhat_mine', [ethers.utils.hexStripZeros(number.toHexString()), '0x0']); + + if (includingEpochsNum! > 1) { + number = number.add(BigNumber.from(includingEpochsNum).sub(1).mul(this.numberOfBlocksInEpoch)); + } + + const numberHex = number.eq(0) ? '0x0' : ethers.utils.hexStripZeros(number.toHexString()); + return network.provider.send('hardhat_mine', [numberHex, '0x0']); } static async setTimestampToPeriodEnding(): Promise { diff --git a/test/helpers/slash.ts b/test/helpers/slash.ts new file mode 100644 index 000000000..c1d59e2b1 --- /dev/null +++ b/test/helpers/slash.ts @@ -0,0 +1,66 @@ +export class IndicatorController { + private _indicators: number[]; + + constructor(_size: number) { + this._indicators = new Array(_size).fill(0); + } + + increaseAt(idx: number, value?: number) { + value = value ?? 1; + this._indicators[idx] += value; + } + + setAt(idx: number, value: number) { + this._indicators[idx] = value; + } + + resetAt(idx: number) { + this._indicators[idx] = 0; + } + + getAt(idx: number): number { + return this._indicators[idx]; + } +} + +export class ScoreController { + private _scores: number[]; + + constructor(_size: number) { + this._scores = new Array(_size).fill(0); + } + + increaseAt(idx: number, value?: number) { + value = value ?? 1; + this._scores[idx] += value; + } + + increaseAtWithUpperbound(idx: number, upperbound: number, value?: number) { + value = value ?? 1; + this._scores[idx] += value; + + if (this._scores[idx] > upperbound) { + this._scores[idx] = upperbound; + } + } + + subAtNonNegative(idx: number, value: number) { + this._scores[idx] -= value; + + if (this._scores[idx] < 0) { + this._scores[idx] = 0; + } + } + + setAt(idx: number, value: number) { + this._scores[idx] = value; + } + + resetAt(idx: number) { + this._scores[idx] = 0; + } + + getAt(idx: number): number { + return this._scores[idx]; + } +} diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index cca8357fb..ae490cd82 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -112,7 +112,9 @@ describe('[Integration] Slash validators', () => { } let tx = slashContract.connect(coinbase).slashUnavailability(slashee.address); - await expect(tx).to.emit(slashContract, 'Slashed').withArgs(slashee.address, SlashType.MISDEMEANOR, period); + await expect(tx) + .to.emit(slashContract, 'Slashed') + .withArgs(slashee.address, SlashType.UNAVAILABILITY_TIER_1, period); await expect(tx) .to.emit(validatorContract, 'ValidatorPunished') .withArgs(slashee.address, period, 0, 0, true, false); @@ -161,7 +163,7 @@ describe('[Integration] Slash validators', () => { await expect(slashValidatorTx) .to.emit(slashContract, 'Slashed') - .withArgs(slashee.address, SlashType.FELONY, period); + .withArgs(slashee.address, SlashType.UNAVAILABILITY_TIER_2, period); let blockNumber = await network.provider.send('eth_blockNumber'); @@ -288,7 +290,7 @@ describe('[Integration] Slash validators', () => { await expect(slashValidatorTx) .to.emit(slashContract, 'Slashed') - .withArgs(slashee.address, SlashType.FELONY, period); + .withArgs(slashee.address, SlashType.UNAVAILABILITY_TIER_2, period); let blockNumber = await network.provider.send('eth_blockNumber'); diff --git a/test/slash/CreditScore.test.ts b/test/slash/CreditScore.test.ts new file mode 100644 index 000000000..9e5458916 --- /dev/null +++ b/test/slash/CreditScore.test.ts @@ -0,0 +1,410 @@ +import { BigNumber, BytesLike, Transaction } from 'ethers'; +import { expect } from 'chai'; +import { ethers, network } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; + +import { + MockRoninValidatorSetOverridePrecompile__factory, + MockSlashIndicatorExtended, + MockSlashIndicatorExtended__factory, + RoninGovernanceAdmin, + RoninGovernanceAdmin__factory, + RoninValidatorSet, + Staking, + Staking__factory, +} from '../../src/types'; +import { initTest } from '../helpers/fixture'; +import { EpochController } from '../helpers/ronin-validator-set'; +import { IndicatorController, ScoreController } from '../helpers/slash'; +import { GovernanceAdminInterface } from '../../src/script/governance-admin-interface'; +import { SlashType } from '../../src/script/slash-indicator'; + +let slashContract: MockSlashIndicatorExtended; +let mockSlashLogic: MockSlashIndicatorExtended; +let stakingContract: Staking; +let governanceAdmin: RoninGovernanceAdmin; +let governanceAdminInterface: GovernanceAdminInterface; + +let coinbase: SignerWithAddress; +let deployer: SignerWithAddress; +let governor: SignerWithAddress; +let validatorContract: RoninValidatorSet; +let vagabond: SignerWithAddress; +let candidateAdmins: SignerWithAddress[]; +let validatorCandidates: SignerWithAddress[]; + +let localIndicatorController: IndicatorController; +let localScoreController: ScoreController; +let localEpochController: EpochController; + +const gainCreditScore = 50; +const maxCreditScore = 600; +const bailOutCostMultiplier = 5; + +const unavailabilityTier1Threshold = 5; +const unavailabilityTier2Threshold = 15; +const slashAmountForUnavailabilityTier2Threshold = 2; + +const minValidatorBalance = BigNumber.from(100); +const maxValidatorCandidate = 3; +const maxValidatorNumber = 2; +const numberOfBlocksInEpoch = 600; +const minOffset = 200; + +const wrapUpEpoch = async () => { + await localEpochController.mineToBeforeEndOfEpoch(); + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + await validatorContract.connect(coinbase).wrapUpEpoch(); +}; + +const endPeriodAndWrapUpAndResetIndicators = async (includingEpochsNum?: number) => { + if (includingEpochsNum) { + expect(includingEpochsNum).gt(0); + } + + await localEpochController.mineToBeforeEndOfEpoch(includingEpochsNum); + await EpochController.setTimestampToPeriodEnding(); + await wrapUpEpoch(); + + validatorCandidates.map((_, i) => localIndicatorController.resetAt(i)); +}; + +const slashUntilValidatorTier = async (slasherIdx: number, slasheeIdx: number, tier: number) => { + if (tier != 1 && tier != 2) { + return; + } + + let _threshold = tier == 1 ? unavailabilityTier1Threshold : unavailabilityTier2Threshold; + let _slashType = tier == 1 ? SlashType.UNAVAILABILITY_TIER_1 : SlashType.UNAVAILABILITY_TIER_2; + + let tx; + let slasher = validatorCandidates[slasherIdx]; + let slashee = validatorCandidates[slasheeIdx]; + + await network.provider.send('hardhat_setCoinbase', [slasher.address]); + + let _toSlashTimes = _threshold - localIndicatorController.getAt(slasheeIdx); + + for (let i = 0; i < _toSlashTimes; i++) { + tx = await slashContract.connect(slasher).slashUnavailability(slashee.address); + } + + let period = await validatorContract.currentPeriod(); + await expect(tx).to.emit(slashContract, 'Slashed').withArgs(slashee.address, _slashType, period); + localIndicatorController.setAt(slasheeIdx, _threshold); +}; + +const validateScoreAt = async (idx: number) => { + expect(await slashContract.getCreditScore(validatorCandidates[idx].address)).to.eq(localScoreController.getAt(idx)); +}; + +const validateIndicatorAt = async (idx: number) => { + expect(await slashContract.currentUnavailabilityIndicator(validatorCandidates[idx].address)).to.eq( + localIndicatorController.getAt(idx) + ); +}; + +describe('Credit score and bail out test', () => { + before(async () => { + [deployer, coinbase, governor, vagabond, ...validatorCandidates] = await ethers.getSigners(); + + candidateAdmins = validatorCandidates.slice(0, maxValidatorCandidate); + validatorCandidates = validatorCandidates.slice(maxValidatorCandidate, maxValidatorCandidate * 2); + + const { slashContractAddress, stakingContractAddress, validatorContractAddress, roninGovernanceAdminAddress } = + await initTest('CreditScore')({ + slashIndicatorArguments: { + unavailabilitySlashing: { + unavailabilityTier1Threshold, + unavailabilityTier2Threshold, + slashAmountForUnavailabilityTier2Threshold, + }, + creditScore: { + gainCreditScore, + maxCreditScore, + bailOutCostMultiplier, + }, + }, + stakingArguments: { + minValidatorBalance, + }, + roninValidatorSetArguments: { + maxValidatorNumber, + numberOfBlocksInEpoch, + maxValidatorCandidate, + }, + maintenanceArguments: { + minOffset, + }, + roninTrustedOrganizationArguments: { + trustedOrganizations: [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), + }, + }); + + stakingContract = Staking__factory.connect(stakingContractAddress, deployer); + validatorContract = MockRoninValidatorSetOverridePrecompile__factory.connect(validatorContractAddress, deployer); + slashContract = MockSlashIndicatorExtended__factory.connect(slashContractAddress, deployer); + governanceAdmin = RoninGovernanceAdmin__factory.connect(roninGovernanceAdminAddress, deployer); + governanceAdminInterface = new GovernanceAdminInterface(governanceAdmin, governor); + + const mockValidatorLogic = await new MockRoninValidatorSetOverridePrecompile__factory(deployer).deploy(); + await mockValidatorLogic.deployed(); + await governanceAdminInterface.upgrade(validatorContract.address, mockValidatorLogic.address); + + mockSlashLogic = await new MockSlashIndicatorExtended__factory(deployer).deploy(); + await mockSlashLogic.deployed(); + await governanceAdminInterface.upgrade(slashContractAddress, mockSlashLogic.address); + + for (let i = 0; i < maxValidatorNumber; i++) { + await stakingContract + .connect(validatorCandidates[i]) + .applyValidatorCandidate( + candidateAdmins[i].address, + validatorCandidates[i].address, + validatorCandidates[i].address, + validatorCandidates[i].address, + 1, + { value: minValidatorBalance.mul(2).sub(i) } + ); + } + + await network.provider.send('hardhat_setCoinbase', [coinbase.address]); + + localEpochController = new EpochController(minOffset, numberOfBlocksInEpoch); + await localEpochController.mineToBeforeEndOfEpoch(); + await validatorContract.connect(coinbase).wrapUpEpoch(); + expect(await validatorContract.getValidators()).eql( + validatorCandidates.slice(0, maxValidatorNumber).map((_) => _.address) + ); + expect(await validatorContract.getBlockProducers()).eql( + validatorCandidates.slice(0, maxValidatorNumber).map((_) => _.address) + ); + + localIndicatorController = new IndicatorController(validatorCandidates.length); + localScoreController = new ScoreController(validatorCandidates.length); + }); + + describe('Counting credit score after each period', async () => { + it('Should the score updated correctly, case: max score (N), in jail (N), unavailability (N)', async () => { + await endPeriodAndWrapUpAndResetIndicators(); + localScoreController.increaseAtWithUpperbound(0, maxCreditScore, gainCreditScore); + await validateScoreAt(0); + }); + it('Should the score updated correctly, case: max score (N), in jail (N), unavailability (y)', async () => { + await network.provider.send('hardhat_setCoinbase', [validatorCandidates[1].address]); + await slashContract.connect(validatorCandidates[1]).slashUnavailability(validatorCandidates[0].address); + localIndicatorController.increaseAt(1); + await endPeriodAndWrapUpAndResetIndicators(); + localScoreController.increaseAtWithUpperbound(0, maxCreditScore, gainCreditScore - 1); + await validateScoreAt(0); + }); + it('Should the score updated correctly, case: max score (N), in jail (y), unavailability (N)', async () => { + await slashUntilValidatorTier(1, 0, 2); + await wrapUpEpoch(); + await endPeriodAndWrapUpAndResetIndicators(); + localScoreController.increaseAtWithUpperbound(0, maxCreditScore, 0); + await validateScoreAt(0); + + let _jailLeft = await validatorContract.jailedTimeLeft(validatorCandidates[0].address); + await network.provider.send('hardhat_mine', [_jailLeft.blockLeft_.toHexString(), '0x0']); + }); + it('Should the score updated correctly, case: max score (y), in jail (N), unavailability (N)', async () => { + for (let i = 0; i < maxCreditScore / gainCreditScore + 1; i++) { + await endPeriodAndWrapUpAndResetIndicators(); + localScoreController.increaseAtWithUpperbound(0, maxCreditScore, gainCreditScore); + await validateScoreAt(0); + } + }); + }); + + describe('Bail out test', async () => { + describe('Sanity check', async () => { + it('Should the non admin candidate cannot call the bail out function', async () => { + await expect( + slashContract.connect(validatorCandidates[0]).bailOut(validatorCandidates[0].address) + ).revertedWith('SlashIndicator: method caller must be a candidate admin'); + }); + it('Should not be able to call the bail out function with param of non-candidate consensus address ', async () => { + await expect(slashContract.connect(candidateAdmins[0]).bailOut(validatorCandidates[2].address)).revertedWith( + 'SlashIndicator: consensus address must be a validator candidate' + ); + }); + }); + + describe('Bailing out from a validator but non-block-producer', async () => { + before(async () => { + expect(await validatorContract.isBlockProducer(validatorCandidates[0].address)).eq(true); + + await slashUntilValidatorTier(1, 0, 2); + await wrapUpEpoch(); + expect(await validatorContract.isBlockProducer(validatorCandidates[0].address)).eq(false); + }); + + it('Should the bailing out cost subtracted correctly', async () => { + let _latestBlockNum = BigNumber.from(await network.provider.send('eth_blockNumber')); + let _jailLeft = await validatorContract.jailedTimeLeftAtBlock( + validatorCandidates[0].address, + _latestBlockNum.add(1) + ); + + let tx = await slashContract.connect(candidateAdmins[0]).bailOut(validatorCandidates[0].address); + let _period = validatorContract.currentPeriod(); + + expect(tx).emit(slashContract, 'BailedOut').withArgs([validatorCandidates[0].address, _period]); + expect(tx).emit(validatorContract, 'ValidatorLiberated').withArgs([validatorCandidates[0].address]); + + localScoreController.subAtNonNegative(0, bailOutCostMultiplier * _jailLeft.epochLeft_.toNumber()); + await validateScoreAt(0); + }); + + it('Should the indicator get reset', async () => { + localIndicatorController.resetAt(0); + await validateIndicatorAt(0); + }); + it.skip('Should the rewards of the validator before the bailout get removed', async () => {}); + it.skip('Should the rewards of the validator after the bailout get cut in half', async () => {}); + + it('Should the bailed out validator becomes block producer in the next epoch', async () => { + await wrapUpEpoch(); + expect(await validatorContract.isBlockProducer(validatorCandidates[0].address)).eq(true); + }); + }); + + describe('Insufficient credit score to bail out', async () => { + before(async () => { + await endPeriodAndWrapUpAndResetIndicators(); + localScoreController.increaseAtWithUpperbound(0, maxCreditScore, gainCreditScore); + await validateScoreAt(0); + expect(await validatorContract.isBlockProducer(validatorCandidates[0].address)).eq(true); + + await slashUntilValidatorTier(1, 0, 2); + expect(await validatorContract.isBlockProducer(validatorCandidates[0].address)).eq(true); + }); + + it('Should not be able to bail out due to insufficient credit score', async () => { + await expect(slashContract.connect(candidateAdmins[0]).bailOut(validatorCandidates[0].address)).revertedWith( + 'SlashIndicator: insufficient credit score to bail out' + ); + }); + + it('Should the slashed validator become block producer when jailed time over', async () => { + let _jailEpochLeft = (await validatorContract.jailedTimeLeft(validatorCandidates[0].address)).epochLeft_; + await endPeriodAndWrapUpAndResetIndicators(_jailEpochLeft.toNumber()); + expect(await validatorContract.isBlockProducer(validatorCandidates[0].address)).eq(true); + }); + }); + + describe('Bailing out from a to-be-in-jail validator', async () => { + before(async () => { + for (let i = 0; i < maxCreditScore / gainCreditScore + 1; i++) { + await endPeriodAndWrapUpAndResetIndicators(); + await localScoreController.increaseAtWithUpperbound(0, maxCreditScore, gainCreditScore); + } + + expect(await validatorContract.isValidator(validatorCandidates[0].address)).eq(true); + expect(await validatorContract.isBlockProducer(validatorCandidates[0].address)).eq(true); + await slashUntilValidatorTier(1, 0, 2); + expect(await validatorContract.isBlockProducer(validatorCandidates[0].address)).eq(true); + }); + + it('Should the bailing out cost subtracted correctly', async () => { + let _jailLeft = await validatorContract.jailedTimeLeft(validatorCandidates[0].address); + let tx = await slashContract.connect(candidateAdmins[0]).bailOut(validatorCandidates[0].address); + let _period = validatorContract.currentPeriod(); + + expect(tx).emit(slashContract, 'BailedOut').withArgs([validatorCandidates[0].address, _period]); + expect(tx).emit(validatorContract, 'ValidatorLiberated').withArgs([validatorCandidates[0].address]); + + localScoreController.subAtNonNegative(0, bailOutCostMultiplier * _jailLeft.epochLeft_.toNumber()); + await validateScoreAt(0); + }); + + it('Should the indicator get reset', async () => { + localIndicatorController.resetAt(0); + await validateIndicatorAt(0); + }); + it.skip('Should the rewards of the validator before the bailout get removed', async () => {}); + it.skip('Should the rewards of the validator after the bailout get cut in half', async () => {}); + + it('Should the bailed out validator still is block producer in the next epoch', async () => { + await wrapUpEpoch(); + expect(await validatorContract.isBlockProducer(validatorCandidates[0].address)).eq(true); + }); + }); + + describe('Bailing out from a kicked validator', async () => {}); + describe('Bailing out from a validator that has been bailed out previously', async () => { + before(async () => { + for (let i = 0; i < maxCreditScore / gainCreditScore + 1; i++) { + await endPeriodAndWrapUpAndResetIndicators(); + await localScoreController.increaseAtWithUpperbound(0, maxCreditScore, gainCreditScore); + } + + expect(await validatorContract.isValidator(validatorCandidates[0].address)).eq(true); + expect(await validatorContract.isBlockProducer(validatorCandidates[0].address)).eq(true); + await slashUntilValidatorTier(1, 0, 2); + expect(await validatorContract.isBlockProducer(validatorCandidates[0].address)).eq(true); + + let _latestBlockNum = BigNumber.from(await network.provider.send('eth_blockNumber')); + let _jailLeft = await validatorContract.jailedTimeLeftAtBlock( + validatorCandidates[0].address, + _latestBlockNum.add(1) + ); + + let _jailEpochLeft = _jailLeft.epochLeft_; + await localEpochController.mineToBeforeEndOfEpoch(_jailEpochLeft.sub(1)); + await wrapUpEpoch(); + + let tx = await slashContract.connect(candidateAdmins[0]).bailOut(validatorCandidates[0].address); + let _period = validatorContract.currentPeriod(); + + expect(tx).emit(slashContract, 'BailedOut').withArgs([validatorCandidates[0].address, _period]); + expect(tx).emit(validatorContract, 'ValidatorLiberated').withArgs([validatorCandidates[0].address]); + + localIndicatorController.resetAt(0); + await validateIndicatorAt(0); + + localScoreController.subAtNonNegative(0, bailOutCostMultiplier * 1); + await validateScoreAt(0); + + await localEpochController.mineToBeforeEndOfEpoch(); + await wrapUpEpoch(); + + expect(await validatorContract.isBlockProducer(validatorCandidates[0].address)).eq(true); + }); + + it('Should the bailed-out-validator not be able to bail out second time in the same period', async () => { + await slashUntilValidatorTier(1, 0, 2); + await expect(slashContract.connect(candidateAdmins[0]).bailOut(validatorCandidates[0].address)).revertedWith( + 'SlashIndicator: validator has bailed out previously' + ); + }); + + it('Should the bailed-out-validator be able to bail out in the next periods', async () => { + await endPeriodAndWrapUpAndResetIndicators(); + + let _latestBlockNum = BigNumber.from(await network.provider.send('eth_blockNumber')); + let _jailLeft = await validatorContract.jailedTimeLeftAtBlock( + validatorCandidates[0].address, + _latestBlockNum.add(1) + ); + + let _jailEpochLeft = _jailLeft.epochLeft_; + let tx = await slashContract.connect(candidateAdmins[0]).bailOut(validatorCandidates[0].address); + let _period = validatorContract.currentPeriod(); + + expect(tx).emit(slashContract, 'BailedOut').withArgs([validatorCandidates[0].address, _period]); + expect(tx).emit(validatorContract, 'ValidatorLiberated').withArgs([validatorCandidates[0].address]); + + localScoreController.subAtNonNegative(0, bailOutCostMultiplier * _jailEpochLeft.toNumber()); + await validateScoreAt(0); + }); + }); + }); +}); diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index 4f216143d..e227e7e15 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -16,6 +16,7 @@ import { import { SlashType } from '../../src/script/slash-indicator'; import { initTest } from '../helpers/fixture'; import { EpochController } from '../helpers/ronin-validator-set'; +import { IndicatorController } from '../helpers/slash'; import { GovernanceAdminInterface } from '../../src/script/governance-admin-interface'; let slashContract: MockSlashIndicatorExtended; @@ -30,7 +31,7 @@ let governor: SignerWithAddress; let validatorContract: RoninValidatorSet; let vagabond: SignerWithAddress; let validatorCandidates: SignerWithAddress[]; -let localIndicators: number[]; +let localIndicators: IndicatorController; let localEpochController: EpochController; const unavailabilityTier1Threshold = 5; @@ -45,22 +46,9 @@ const slashDoubleSignAmount = BigNumber.from(5); const minOffset = 200; -const increaseLocalCounterForValidatorAt = (idx: number, value?: number) => { - value = value ?? 1; - localIndicators[idx] += value; -}; - -const setLocalCounterForValidatorAt = (idx: number, value: number) => { - localIndicators[idx] = value; -}; - -const resetLocalCounterForValidatorAt = (idx: number) => { - localIndicators[idx] = 0; -}; - const validateIndicatorAt = async (idx: number) => { expect(await slashContract.currentUnavailabilityIndicator(validatorCandidates[idx].address)).to.eq( - localIndicators[idx] + localIndicators.getAt(idx) ); }; @@ -137,7 +125,7 @@ describe('Slash indicator test', () => { await validatorContract.connect(coinbase).wrapUpEpoch(); expect(await validatorContract.getValidators()).eql(validatorCandidates.map((_) => _.address)); - localIndicators = Array(validatorCandidates.length).fill(0); + localIndicators = new IndicatorController(validatorCandidates.length); }); after(async () => { @@ -164,7 +152,7 @@ describe('Slash indicator test', () => { .connect(validatorCandidates[slasherIdx]) .slashUnavailability(validatorCandidates[slasheeIdx].address); await expect(tx).to.not.emit(slashContract, 'Slashed'); - setLocalCounterForValidatorAt(slasheeIdx, 1); + localIndicators.setAt(slasheeIdx, 1); await validateIndicatorAt(slasheeIdx); }); @@ -174,7 +162,7 @@ describe('Slash indicator test', () => { .connect(validatorCandidates[slasherIdx]) .slashUnavailability(validatorCandidates[slasherIdx].address); - resetLocalCounterForValidatorAt(slasherIdx); + localIndicators.resetAt(slasherIdx); await validateIndicatorAt(slasherIdx); }); @@ -194,7 +182,7 @@ describe('Slash indicator test', () => { await network.provider.send('evm_mine'); await network.provider.send('evm_setAutomine', [true]); - increaseLocalCounterForValidatorAt(slasheeIdx); + localIndicators.increaseAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); }); @@ -215,9 +203,9 @@ describe('Slash indicator test', () => { await network.provider.send('evm_mine'); await network.provider.send('evm_setAutomine', [true]); - increaseLocalCounterForValidatorAt(slasheeIdx1); + localIndicators.increaseAt(slasheeIdx1); await validateIndicatorAt(slasheeIdx1); - setLocalCounterForValidatorAt(slasheeIdx2, 1); + localIndicators.setAt(slasheeIdx2, 1); await validateIndicatorAt(slasheeIdx1); }); }); @@ -238,8 +226,8 @@ describe('Slash indicator test', () => { let period = await validatorContract.currentPeriod(); await expect(tx) .to.emit(slashContract, 'Slashed') - .withArgs(validatorCandidates[slasheeIdx].address, SlashType.MISDEMEANOR, period); - setLocalCounterForValidatorAt(slasheeIdx, unavailabilityTier1Threshold); + .withArgs(validatorCandidates[slasheeIdx].address, SlashType.UNAVAILABILITY_TIER_1, period); + localIndicators.setAt(slasheeIdx, unavailabilityTier1Threshold); await validateIndicatorAt(slasheeIdx); }); @@ -252,7 +240,7 @@ describe('Slash indicator test', () => { tx = await slashContract .connect(validatorCandidates[slasherIdx]) .slashUnavailability(validatorCandidates[slasheeIdx].address); - increaseLocalCounterForValidatorAt(slasheeIdx); + localIndicators.increaseAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); await expect(tx).not.to.emit(slashContract, 'Slashed'); @@ -275,14 +263,14 @@ describe('Slash indicator test', () => { if (i == unavailabilityTier1Threshold - 1) { await expect(tx) .to.emit(slashContract, 'Slashed') - .withArgs(validatorCandidates[slasheeIdx].address, SlashType.MISDEMEANOR, period); + .withArgs(validatorCandidates[slasheeIdx].address, SlashType.UNAVAILABILITY_TIER_1, period); } } await expect(tx) .to.emit(slashContract, 'Slashed') - .withArgs(validatorCandidates[slasheeIdx].address, SlashType.FELONY, period); - setLocalCounterForValidatorAt(slasheeIdx, unavailabilityTier2Threshold); + .withArgs(validatorCandidates[slasheeIdx].address, SlashType.UNAVAILABILITY_TIER_2, period); + localIndicators.setAt(slasheeIdx, unavailabilityTier2Threshold); await validateIndicatorAt(slasheeIdx); }); @@ -295,7 +283,7 @@ describe('Slash indicator test', () => { tx = await slashContract .connect(validatorCandidates[slasherIdx]) .slashUnavailability(validatorCandidates[slasheeIdx].address); - increaseLocalCounterForValidatorAt(slasheeIdx); + localIndicators.increaseAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); await expect(tx).not.to.emit(slashContract, 'Slashed'); @@ -315,14 +303,14 @@ describe('Slash indicator test', () => { .slashUnavailability(validatorCandidates[slasheeIdx].address); } - setLocalCounterForValidatorAt(slasheeIdx, numberOfSlashing); + localIndicators.setAt(slasheeIdx, numberOfSlashing); await validateIndicatorAt(slasheeIdx); await EpochController.setTimestampToPeriodEnding(); await localEpochController.mineToBeforeEndOfEpoch(); await validatorContract.connect(validatorCandidates[slasherIdx]).wrapUpEpoch(); - resetLocalCounterForValidatorAt(slasheeIdx); + localIndicators.resetAt(slasheeIdx); await validateIndicatorAt(slasheeIdx); }); @@ -341,7 +329,7 @@ describe('Slash indicator test', () => { } for (let j = 0; j < slasheeIdxs.length; j++) { - setLocalCounterForValidatorAt(slasheeIdxs[j], numberOfSlashing); + localIndicators.setAt(slasheeIdxs[j], numberOfSlashing); await validateIndicatorAt(slasheeIdxs[j]); } @@ -350,7 +338,7 @@ describe('Slash indicator test', () => { await validatorContract.connect(validatorCandidates[slasherIdx]).wrapUpEpoch(); for (let j = 0; j < slasheeIdxs.length; j++) { - resetLocalCounterForValidatorAt(slasheeIdxs[j]); + localIndicators.resetAt(slasheeIdxs[j]); await validateIndicatorAt(slasheeIdxs[j]); } }); From ee6e3eb2219eac15ac055413097b72d439a4c3f9 Mon Sep 17 00:00:00 2001 From: Bao Date: Wed, 2 Nov 2022 15:19:21 +0700 Subject: [PATCH 183/190] [StakingVesting] Request bonus should not be reverted (#45) * Merge two bonus requests bonus into one * Rename in tests * Rename deploy scripts * Use unsafeSendRON for staking vesting * Add tests * Fix rebase * Add contract balance for transfer failed event * Handle no request bonus for deprecated block producer --- contracts/extensions/RONTransferHelper.sol | 24 +- contracts/interfaces/IStakingVesting.sol | 82 ++--- contracts/ronin/StakingVesting.sol | 98 +++-- .../ronin/validator/RoninValidatorSet.sol | 40 ++- src/config.ts | 2 +- .../{bonus-reward.ts => staking-vesting.ts} | 0 ...ward-proxy.ts => staking-vesting-proxy.ts} | 2 +- src/utils.ts | 2 +- test/helpers/fixture.ts | 2 +- test/helpers/staking-vesting.ts | 74 ++++ test/integration/ActionSubmitReward.test.ts | 6 +- test/integration/Configuration.test.ts | 10 +- test/validator/RoninValidatorSet.test.ts | 334 +++++++++++------- 13 files changed, 412 insertions(+), 264 deletions(-) rename src/deploy/logic/{bonus-reward.ts => staking-vesting.ts} (100%) rename src/deploy/proxy/{bonus-reward-proxy.ts => staking-vesting-proxy.ts} (95%) create mode 100644 test/helpers/staking-vesting.ts diff --git a/contracts/extensions/RONTransferHelper.sol b/contracts/extensions/RONTransferHelper.sol index 75e64508a..766110845 100644 --- a/contracts/extensions/RONTransferHelper.sol +++ b/contracts/extensions/RONTransferHelper.sol @@ -3,6 +3,14 @@ pragma solidity ^0.8.9; abstract contract RONTransferHelper { + /** + * @dev See `_sendRON`. + * Reverts if the recipient does not receive RON. + */ + function _transferRON(address payable _recipient, uint256 _amount) internal { + require(_sendRON(_recipient, _amount), "RONTransfer: unable to transfer value, recipient may have reverted"); + } + /** * @dev Send `_amount` RON to the address `_recipient`. * Returns whether the recipient receives RON or not. @@ -13,14 +21,20 @@ abstract contract RONTransferHelper { */ function _sendRON(address payable _recipient, uint256 _amount) internal returns (bool _success) { require(address(this).balance >= _amount, "RONTransfer: insufficient balance"); - (_success, ) = _recipient.call{ value: _amount }(""); + return _unsafeSendRON(_recipient, _amount); } /** - * @dev See `_sendRON`. - * Reverts if the recipient does not receive RON. + * @dev Unsafe send `_amount` RON to the address `_recipient`. If the sender's balance is insufficient, + * the call does not revert. + * + * Note: + * - Does not assert whether the balance of sender is sufficient. + * - Does not assert whether the recipient accepts RON. + * - Consider using `ReentrancyGuard` before calling this function. + * */ - function _transferRON(address payable _recipient, uint256 _amount) internal { - require(_sendRON(_recipient, _amount), "RONTransfer: unable to transfer value, recipient may have reverted"); + function _unsafeSendRON(address payable _recipient, uint256 _amount) internal returns (bool _success) { + (_success, ) = _recipient.call{ value: _amount }(""); } } diff --git a/contracts/interfaces/IStakingVesting.sol b/contracts/interfaces/IStakingVesting.sol index 6f6c9c879..f0056ac56 100644 --- a/contracts/interfaces/IStakingVesting.sol +++ b/contracts/interfaces/IStakingVesting.sol @@ -3,19 +3,30 @@ pragma solidity ^0.8.9; interface IStakingVesting { - /// @dev Emitted when the block bonus for validator is transferred. - event ValidatorBonusTransferred(uint256 indexed blockNumber, address indexed recipient, uint256 amount); - /// @dev Emitted when the block bonus for bridge operator is transferred. - event BridgeOperatorBonusTransferred(uint256 indexed blockNumber, address indexed recipient, uint256 amount); - /// @dev Emitted when the block bonus for validator is updated - event ValidatorBonusPerBlockUpdated(uint256); + /// @dev Emitted when the block bonus for block producer is transferred. + event BonusTransferred( + uint256 indexed blockNumber, + address indexed recipient, + uint256 blockProducerAmount, + uint256 bridgeOperatorAmount + ); + /// @dev Emitted when the transfer of block bonus for block producer is failed. + event BonusTransferFailed( + uint256 indexed blockNumber, + address indexed recipient, + uint256 blockProducerAmount, + uint256 bridgeOperatorAmount, + uint256 contractBalance + ); + /// @dev Emitted when the block bonus for block producer is updated + event BlockProducerBonusPerBlockUpdated(uint256); /// @dev Emitted when the block bonus for bridge operator is updated event BridgeOperatorBonusPerBlockUpdated(uint256); /** - * @dev Returns the bonus amount for the validator at `_block`. + * @dev Returns the bonus amount for the block producer at `_block`. */ - function validatorBlockBonus(uint256 _block) external view returns (uint256); + function blockProducerBlockBonus(uint256 _block) external view returns (uint256); /** * @dev Returns the bonus amount for the bridge validator at `_block`. @@ -28,51 +39,36 @@ interface IStakingVesting { function receiveRON() external payable; /** - * @dev Returns the last block number that the staking vesting is sent for the validator. + * @dev Returns the last block number that the staking vesting is sent. */ - function lastBlockSendingValidatorBonus() external view returns (uint256); + function lastBlockSendingBonus() external view returns (uint256); /** - * @dev Returns the last block number that the staking vesting is sent for the bridge operator. - */ - function lastBlockSendingBridgeOperatorBonus() external view returns (uint256); - - /** - * @dev Does two actions as in `requestValidatorBonus` and `requestBridgeOperatorBonus`. Returns - * two amounts of bonus correspondingly. - * - * Requirements: - * - The method caller is validator contract. - * - The method must be called only once per block. - * - * Emits the event `ValidatorBonusTransferred` and/or `BridgeOperatorBonusTransferred` - * - */ - function requestBonus() external returns (uint256 _validatorBonus, uint256 _bridgeOperatorBonus); - - /** - * @dev Transfers the staking vesting for the validator whenever a new block is mined. - * Returns the amount of RON sent to validator contract. + * @dev Transfers the staking vesting for the block producer and the bridge operator whenever a new block is mined. * * Requirements: - * - The method caller is validator contract. + * - The method caller must be validator contract. * - The method must be called only once per block. * - * Emits the event `ValidatorBonusTransferred`. + * Emits the event `BonusTransferred` or `BonusTransferFailed`. * - */ - function requestValidatorBonus() external returns (uint256); - - /** - * @dev Transfers the staking vesting for the bridge operator whenever a new block is mined. - * Returns the amount of RON sent to validator contract. + * Notes: + * - The method does not revert when the contract balance is insufficient to send bonus. This assure the submit reward method + * will not be reverted, and the underlying nodes does not hang. * - * Requirements: - * - The method caller is validator contract. - * - The method must be called only once per block. + * @param _forBlockProducer Indicates whether requesting the bonus for the block procucer, in case of being in jail or relevance. + * @param _forBridgeOperator Indicates whether requesting the bonus for the bridge operator. * - * Emits the event `BridgeOperatorBonusTransferred`. + * @return _success Whether the transfer is successfully. This returns false mostly because this contract is out of balance. + * @return _blockProducerBonus The amount of bonus actually sent for the block producer, returns 0 when the transfer is failed. + * @return _bridgeOperatorBonus The amount of bonus actually sent for the bridge operator, returns 0 when the transfer is failed. * */ - function requestBridgeOperatorBonus() external returns (uint256); + function requestBonus(bool _forBlockProducer, bool _forBridgeOperator) + external + returns ( + bool _success, + uint256 _blockProducerBonus, + uint256 _bridgeOperatorBonus + ); } diff --git a/contracts/ronin/StakingVesting.sol b/contracts/ronin/StakingVesting.sol index 0afa2562c..8812a6382 100644 --- a/contracts/ronin/StakingVesting.sol +++ b/contracts/ronin/StakingVesting.sol @@ -8,14 +8,12 @@ import "../extensions/collections/HasValidatorContract.sol"; import "../extensions/RONTransferHelper.sol"; contract StakingVesting is IStakingVesting, HasValidatorContract, RONTransferHelper, Initializable { - /// @dev The block bonus for the validator whenever a new block is mined. - uint256 internal _validatorBonusPerBlock; + /// @dev The block bonus for the block producer whenever a new block is mined. + uint256 internal _blockProducerBonusPerBlock; /// @dev The block bonus for the bridge operator whenever a new block is mined. uint256 internal _bridgeOperatorBonusPerBlock; - /// @dev The last block number that the staking vesting for validator sent. - uint256 public lastBlockSendingValidatorBonus; - /// @dev The last block number that the staking vesting for bridge operator sent. - uint256 public lastBlockSendingBridgeOperatorBonus; + /// @dev The last block number that the staking vesting sent. + uint256 public lastBlockSendingBonus; constructor() { _disableInitializers(); @@ -26,11 +24,11 @@ contract StakingVesting is IStakingVesting, HasValidatorContract, RONTransferHel */ function initialize( address __validatorContract, - uint256 __validatorBonusPerBlock, + uint256 __blockProducerBonusPerBlock, uint256 __bridgeOperatorBonusPerBlock ) external payable initializer { _setValidatorContract(__validatorContract); - _setValidatorBonusPerBlock(__validatorBonusPerBlock); + _setBlockProducerBonusPerBlock(__blockProducerBonusPerBlock); _setBridgeOperatorBonusPerBlock(__bridgeOperatorBonusPerBlock); } @@ -42,10 +40,10 @@ contract StakingVesting is IStakingVesting, HasValidatorContract, RONTransferHel /** * @inheritdoc IStakingVesting */ - function validatorBlockBonus( + function blockProducerBlockBonus( uint256 /* _block */ - ) public view returns (uint256) { - return _validatorBonusPerBlock; + ) public view override returns (uint256) { + return _blockProducerBonusPerBlock; } /** @@ -53,70 +51,60 @@ contract StakingVesting is IStakingVesting, HasValidatorContract, RONTransferHel */ function bridgeOperatorBlockBonus( uint256 /* _block */ - ) public view returns (uint256) { + ) public view override returns (uint256) { return _bridgeOperatorBonusPerBlock; } /** * @inheritdoc IStakingVesting */ - function requestBonus() + function requestBonus(bool _forBlockProducer, bool _forBridgeOperator) external + override onlyValidatorContract - returns (uint256 _validatorBonus, uint256 _bridgeOperatorBonus) + returns ( + bool _success, + uint256 _blockProducerBonus, + uint256 _bridgeOperatorBonus + ) { - _validatorBonus = requestValidatorBonus(); - _bridgeOperatorBonus = requestBridgeOperatorBonus(); - } + require(block.number > lastBlockSendingBonus, "StakingVesting: bonus for already sent"); + lastBlockSendingBonus = block.number; - /** - * @inheritdoc IStakingVesting - */ - function requestValidatorBonus() public onlyValidatorContract returns (uint256 _amount) { - require(block.number > lastBlockSendingValidatorBonus, "StakingVesting: bonus for validator already sent"); - lastBlockSendingValidatorBonus = block.number; - _amount = validatorBlockBonus(block.number); + _blockProducerBonus = _forBlockProducer ? blockProducerBlockBonus(block.number) : 0; + _bridgeOperatorBonus = _forBridgeOperator ? bridgeOperatorBlockBonus(block.number) : 0; - if (_amount > 0) { - address payable _validatorContractAddr = payable(validatorContract()); - require( - _sendRON(_validatorContractAddr, _amount), - "StakingVesting: could not transfer RON to validator contract" - ); - emit ValidatorBonusTransferred(block.number, _validatorContractAddr, _amount); - } - } + uint256 _totalAmount = _blockProducerBonus + _bridgeOperatorBonus; - /** - * @inheritdoc IStakingVesting - */ - function requestBridgeOperatorBonus() public onlyValidatorContract returns (uint256 _amount) { - require( - block.number > lastBlockSendingBridgeOperatorBonus, - "StakingVesting: bonus for bridge operator already sent" - ); - lastBlockSendingBridgeOperatorBonus = block.number; - _amount = bridgeOperatorBlockBonus(block.number); - - if (_amount > 0) { + if (_totalAmount > 0) { address payable _validatorContractAddr = payable(validatorContract()); - require( - _sendRON(_validatorContractAddr, _amount), - "StakingVesting: could not transfer RON to validator contract" - ); - emit BridgeOperatorBonusTransferred(block.number, _validatorContractAddr, _amount); + + _success = _unsafeSendRON(_validatorContractAddr, _totalAmount); + + if (!_success) { + emit BonusTransferFailed( + block.number, + _validatorContractAddr, + _blockProducerBonus, + _bridgeOperatorBonus, + address(this).balance + ); + return (_success, 0, 0); + } + + emit BonusTransferred(block.number, _validatorContractAddr, _blockProducerBonus, _bridgeOperatorBonus); } } /** - * @dev Sets the bonus amount per block for validator. + * @dev Sets the bonus amount per block for block producer. * - * Emits the event `ValidatorBonusPerBlockUpdated`. + * Emits the event `BlockProducerBonusPerBlockUpdated`. * */ - function _setValidatorBonusPerBlock(uint256 _amount) internal { - _validatorBonusPerBlock = _amount; - emit ValidatorBonusPerBlockUpdated(_amount); + function _setBlockProducerBonusPerBlock(uint256 _amount) internal { + _blockProducerBonusPerBlock = _amount; + emit BlockProducerBonusPerBlockUpdated(_amount); } /** diff --git a/contracts/ronin/validator/RoninValidatorSet.sol b/contracts/ronin/validator/RoninValidatorSet.sol index 8ca765989..1bda14b7f 100644 --- a/contracts/ronin/validator/RoninValidatorSet.sol +++ b/contracts/ronin/validator/RoninValidatorSet.sol @@ -140,31 +140,33 @@ contract RoninValidatorSet is function submitBlockReward() external payable override onlyCoinbase { uint256 _submittedReward = msg.value; address _coinbaseAddr = msg.sender; + bool _requestForBlockProducer = isBlockProducer(_coinbaseAddr) && + !_jailed(_coinbaseAddr) && + !_miningRewardDeprecated(_coinbaseAddr, currentPeriod()); + bool _requestForBridgeOperator = true; + + (, uint256 _blockProducerBonus, uint256 _bridgeOperatorBonus) = _stakingVestingContract.requestBonus( + _requestForBlockProducer, + _requestForBridgeOperator + ); - uint256 _bridgeOperatorBonus = _stakingVestingContract.requestBridgeOperatorBonus(); _totalBridgeReward += _bridgeOperatorBonus; // Deprecates reward for non-validator or slashed validator - if ( - !isBlockProducer(_coinbaseAddr) || - _jailed(_coinbaseAddr) || - _miningRewardDeprecated(_coinbaseAddr, currentPeriod()) - ) { + if (_requestForBlockProducer) { + uint256 _reward = _submittedReward + _blockProducerBonus; + uint256 _rate = _candidateInfo[_coinbaseAddr].commissionRate; + + uint256 _miningAmount = (_rate * _reward) / 100_00; + _miningReward[_coinbaseAddr] += _miningAmount; + + uint256 _delegatingAmount = _reward - _miningAmount; + _delegatingReward[_coinbaseAddr] += _delegatingAmount; + _stakingContract.recordReward(_coinbaseAddr, _delegatingAmount); + emit BlockRewardSubmitted(_coinbaseAddr, _submittedReward, _blockProducerBonus); + } else { emit BlockRewardRewardDeprecated(_coinbaseAddr, _submittedReward); - return; } - - uint256 _blockProducerBonus = _stakingVestingContract.requestValidatorBonus(); - uint256 _reward = _submittedReward + _blockProducerBonus; - uint256 _rate = _candidateInfo[_coinbaseAddr].commissionRate; - - uint256 _miningAmount = (_rate * _reward) / 100_00; - _miningReward[_coinbaseAddr] += _miningAmount; - - uint256 _delegatingAmount = _reward - _miningAmount; - _delegatingReward[_coinbaseAddr] += _delegatingAmount; - _stakingContract.recordReward(_coinbaseAddr, _delegatingAmount); - emit BlockRewardSubmitted(_coinbaseAddr, _submittedReward, _blockProducerBonus); } /** diff --git a/src/config.ts b/src/config.ts index ced60ce7d..2127201af 100644 --- a/src/config.ts +++ b/src/config.ts @@ -73,7 +73,7 @@ export const stakingConfig: StakingConfig = { export const stakingVestingConfig: StakingVestingConfig = { [Network.Hardhat]: undefined, [Network.Devnet]: { - validatorBonusPerBlock: BigNumber.from(10).pow(18), // 1 RON per block + blockProducerBonusPerBlock: BigNumber.from(10).pow(18), // 1 RON per block bridgeOperatorBonusPerBlock: BigNumber.from(10).pow(18), // 1 RON per block topupAmount: BigNumber.from(10).pow(18).mul(BigNumber.from(10).pow(4)), // 10.000 RON }, diff --git a/src/deploy/logic/bonus-reward.ts b/src/deploy/logic/staking-vesting.ts similarity index 100% rename from src/deploy/logic/bonus-reward.ts rename to src/deploy/logic/staking-vesting.ts diff --git a/src/deploy/proxy/bonus-reward-proxy.ts b/src/deploy/proxy/staking-vesting-proxy.ts similarity index 95% rename from src/deploy/proxy/bonus-reward-proxy.ts rename to src/deploy/proxy/staking-vesting-proxy.ts index f0abf65d4..cee574cc7 100644 --- a/src/deploy/proxy/bonus-reward-proxy.ts +++ b/src/deploy/proxy/staking-vesting-proxy.ts @@ -18,7 +18,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const data = new StakingVesting__factory().interface.encodeFunctionData('initialize', [ generalRoninConf[network.name]!.validatorContract?.address, - stakingVestingConfig[network.name]!.validatorBonusPerBlock, + stakingVestingConfig[network.name]!.blockProducerBonusPerBlock, stakingVestingConfig[network.name]!.bridgeOperatorBonusPerBlock, ]); diff --git a/src/utils.ts b/src/utils.ts index ef28db3f0..1558ff0e0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -79,7 +79,7 @@ export interface StakingConfig { } export interface StakingVestingArguments { - validatorBonusPerBlock?: BigNumberish; + blockProducerBonusPerBlock?: BigNumberish; bridgeOperatorBonusPerBlock?: BigNumberish; topupAmount?: BigNumberish; } diff --git a/test/helpers/fixture.ts b/test/helpers/fixture.ts index f3dacb7b9..37f85b264 100644 --- a/test/helpers/fixture.ts +++ b/test/helpers/fixture.ts @@ -66,7 +66,7 @@ export const defaultTestConfig: InitTestInput = { }, stakingVestingArguments: { - validatorBonusPerBlock: 1, + blockProducerBonusPerBlock: 1, bridgeOperatorBonusPerBlock: 1, topupAmount: BigNumber.from(100000000000), }, diff --git a/test/helpers/staking-vesting.ts b/test/helpers/staking-vesting.ts new file mode 100644 index 000000000..721ae60ec --- /dev/null +++ b/test/helpers/staking-vesting.ts @@ -0,0 +1,74 @@ +import { expect } from 'chai'; +import { BigNumberish, ContractTransaction } from 'ethers'; + +import { expectEvent } from './utils'; +import { StakingVesting__factory } from '../../src/types'; +import { Address } from 'hardhat-deploy/dist/types'; + +const contractInterface = StakingVesting__factory.createInterface(); + +export const expects = { + emitBonusTransferredEvent: async function ( + tx: ContractTransaction, + blockNumber?: BigNumberish, + recipient?: Address, + blockProducerBonus?: BigNumberish, + bridgeOperatorBonus?: BigNumberish + ) { + const eventName = 'BonusTransferred'; + await expectEvent( + contractInterface, + eventName, + tx, + (event) => { + if (blockNumber) { + expect(event.args[0], eventName + ': invalid block number').eql(blockNumber); + } + if (recipient) { + expect(event.args[1], eventName + ': invalid recipient').eql(recipient); + } + if (blockProducerBonus) { + expect(event.args[2], eventName + ': invalid block producer bonus').eql(blockProducerBonus); + } + if (bridgeOperatorBonus) { + expect(event.args[3], eventName + ': invalid bridge operator bonus').eql(bridgeOperatorBonus); + } + }, + 1 + ); + }, + + emitBonusTransferFailedEvent: async function ( + tx: ContractTransaction, + blockNumber?: BigNumberish, + recipient?: Address, + blockProducerBonus?: BigNumberish, + bridgeOperatorBonus?: BigNumberish, + contractBalance?: BigNumberish + ) { + const eventName = 'BonusTransferFailed'; + await expectEvent( + contractInterface, + eventName, + tx, + (event) => { + if (blockNumber) { + expect(event.args[0], eventName + ': invalid block number').eql(blockNumber); + } + if (recipient) { + expect(event.args[1], eventName + ': invalid recipient').eql(recipient); + } + if (blockProducerBonus) { + expect(event.args[2], eventName + ': invalid block producer bonus').eql(blockProducerBonus); + } + if (bridgeOperatorBonus) { + expect(event.args[3], eventName + ': invalid bridge operator bonus').eql(bridgeOperatorBonus); + } + if (bridgeOperatorBonus) { + expect(event.args[4], eventName + ': invalid contract balance').eql(contractBalance); + } + }, + 1 + ); + }, +}; diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index 461895384..f47c853a4 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -34,7 +34,7 @@ const unavailabilityTier2Threshold = 10; const slashAmountForUnavailabilityTier2Threshold = BigNumber.from(1); const slashDoubleSignAmount = 1000; const minValidatorBalance = BigNumber.from(100); -const validatorBonusPerBlock = BigNumber.from(1); +const blockProducerBonusPerBlock = BigNumber.from(1); describe('[Integration] Submit Block Reward', () => { const blockRewardAmount = BigNumber.from(2); @@ -58,7 +58,7 @@ describe('[Integration] Submit Block Reward', () => { minValidatorBalance, }, stakingVestingArguments: { - validatorBonusPerBlock, + blockProducerBonusPerBlock, }, roninTrustedOrganizationArguments: { trustedOrganizations: [governor].map((v) => ({ @@ -139,7 +139,7 @@ describe('[Integration] Submit Block Reward', () => { it('Should the ValidatorSetContract emit event of submitting reward', async () => { await expect(submitRewardTx) .to.emit(validatorContract, 'BlockRewardSubmitted') - .withArgs(validator.address, blockRewardAmount, validatorBonusPerBlock); + .withArgs(validator.address, blockRewardAmount, blockProducerBonusPerBlock); }); it.skip('Should the ValidatorSetContract update mining reward', async () => {}); diff --git a/test/integration/Configuration.test.ts b/test/integration/Configuration.test.ts index 147e6a400..87df09058 100644 --- a/test/integration/Configuration.test.ts +++ b/test/integration/Configuration.test.ts @@ -53,7 +53,7 @@ const config: InitTestInput = { minValidatorBalance: BigNumber.from(100), }, stakingVestingArguments: { - validatorBonusPerBlock: 1, + blockProducerBonusPerBlock: 1, bridgeOperatorBonusPerBlock: 1, topupAmount: BigNumber.from(10000), }, @@ -196,11 +196,11 @@ describe('[Integration] Configuration check', () => { it('Should the StakingVestingContract contract set configs correctly', async () => { expect(await stakingVestingContract.validatorContract()).eq(validatorContract.address); - expect(await stakingVestingContract.validatorBlockBonus(0)).eq( - config.stakingVestingArguments?.validatorBonusPerBlock + expect(await stakingVestingContract.blockProducerBlockBonus(0)).eq( + config.stakingVestingArguments?.blockProducerBonusPerBlock ); - expect(await stakingVestingContract.validatorBlockBonus(Math.floor(Math.random() * 1_000_000))).eq( - config.stakingVestingArguments?.validatorBonusPerBlock + expect(await stakingVestingContract.blockProducerBlockBonus(Math.floor(Math.random() * 1_000_000))).eq( + config.stakingVestingArguments?.blockProducerBonusPerBlock ); expect(await stakingVestingContract.bridgeOperatorBlockBonus(0)).eq( config.stakingVestingArguments?.bridgeOperatorBonusPerBlock diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index 7dd32b71d..310617b8b 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -12,14 +12,18 @@ import { MockSlashIndicatorExtended, RoninGovernanceAdmin__factory, RoninGovernanceAdmin, + StakingVesting__factory, + StakingVesting, } from '../../src/types'; import * as RoninValidatorSet from '../helpers/ronin-validator-set'; +import { expects as StakingVestingExpects } from '../helpers/staking-vesting'; import { mineBatchTxs } from '../helpers/utils'; import { initTest } from '../helpers/fixture'; import { GovernanceAdminInterface } from '../../src/script/governance-admin-interface'; import { Address } from 'hardhat-deploy/dist/types'; let roninValidatorSet: MockRoninValidatorSetExtended; +let stakingVesting: StakingVesting; let stakingContract: Staking; let slashIndicator: MockSlashIndicatorExtended; let governanceAdmin: RoninGovernanceAdmin; @@ -41,8 +45,10 @@ const slashAmountForUnavailabilityTier2Threshold = 100; const maxValidatorNumber = 4; const maxValidatorCandidate = 100; const minValidatorBalance = BigNumber.from(20000); -const validatorBonusPerBlock = BigNumber.from(5000); +const blockProducerBonusPerBlock = BigNumber.from(5000); const bridgeOperatorBonusPerBlock = BigNumber.from(37); +const zeroTopUpAmount = 0; +const topUpAmount = BigNumber.from(100000000000); describe('Ronin Validator Set test', () => { before(async () => { @@ -50,36 +56,43 @@ describe('Ronin Validator Set test', () => { validatorCandidates = validatorCandidates.slice(0, localValidatorCandidatesLength); await network.provider.send('hardhat_setCoinbase', [coinbase.address]); - const { slashContractAddress, validatorContractAddress, stakingContractAddress, roninGovernanceAdminAddress } = - await initTest('RoninValidatorSet')({ - slashIndicatorArguments: { - unavailabilitySlashing: { - slashAmountForUnavailabilityTier2Threshold, - }, + const { + slashContractAddress, + validatorContractAddress, + stakingContractAddress, + roninGovernanceAdminAddress, + stakingVestingContractAddress, + } = await initTest('RoninValidatorSet')({ + slashIndicatorArguments: { + unavailabilitySlashing: { + slashAmountForUnavailabilityTier2Threshold, }, - stakingArguments: { - minValidatorBalance, - }, - stakingVestingArguments: { - validatorBonusPerBlock, - bridgeOperatorBonusPerBlock, - }, - roninValidatorSetArguments: { - maxValidatorNumber, - maxValidatorCandidate, - }, - roninTrustedOrganizationArguments: { - trustedOrganizations: [governor].map((v) => ({ - consensusAddr: v.address, - governor: v.address, - bridgeVoter: v.address, - weight: 100, - addedBlock: 0, - })), - }, - }); + }, + stakingArguments: { + minValidatorBalance, + }, + stakingVestingArguments: { + blockProducerBonusPerBlock, + bridgeOperatorBonusPerBlock, + topupAmount: zeroTopUpAmount, + }, + roninValidatorSetArguments: { + maxValidatorNumber, + maxValidatorCandidate, + }, + roninTrustedOrganizationArguments: { + trustedOrganizations: [governor].map((v) => ({ + consensusAddr: v.address, + governor: v.address, + bridgeVoter: v.address, + weight: 100, + addedBlock: 0, + })), + }, + }); roninValidatorSet = MockRoninValidatorSetExtended__factory.connect(validatorContractAddress, deployer); + stakingVesting = StakingVesting__factory.connect(stakingVestingContractAddress, deployer); slashIndicator = MockSlashIndicatorExtended__factory.connect(slashContractAddress, deployer); stakingContract = Staking__factory.connect(stakingContractAddress, deployer); governanceAdmin = RoninGovernanceAdmin__factory.connect(roninGovernanceAdminAddress, deployer); @@ -243,144 +256,205 @@ describe('Ronin Validator Set test', () => { }); describe('Recording and distributing rewards', async () => { - it('Should not be able to submit block reward using unauthorized account', async () => { - await expect(roninValidatorSet.submitBlockReward()).revertedWith( - 'RoninValidatorSet: method caller must be coinbase' - ); + describe('Sanity check', async () => { + it('Should not be able to submit block reward using unauthorized account', async () => { + await expect(roninValidatorSet.submitBlockReward()).revertedWith( + 'RoninValidatorSet: method caller must be coinbase' + ); + }); }); - it('Should be able to submit block reward using coinbase account', async () => { - const tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); - await RoninValidatorSet.expects.emitBlockRewardSubmittedEvent(tx, coinbase.address, 100, validatorBonusPerBlock); + describe('Submit block reward when staking vesting is insufficient', async () => { + it('Should be able to submit block reward using coinbase account and not receive bonuses', async () => { + const tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + + epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); + lastPeriod = await roninValidatorSet.currentPeriod(); + await RoninValidatorSet.expects.emitBlockRewardSubmittedEvent(tx, coinbase.address, 100, 0); + + expect(tx).not.emit(stakingVesting, 'BonusTransferred'); + await StakingVestingExpects.emitBonusTransferFailedEvent( + tx, + undefined, + roninValidatorSet.address, + blockProducerBonusPerBlock, + bridgeOperatorBonusPerBlock, + BigNumber.from(0) + ); + }); }); - it('Should be able to get right reward at the end of period', async () => { - const balance = await treasury.getBalance(); - let tx: ContractTransaction; - await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); - lastPeriod = await roninValidatorSet.currentPeriod(); - epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); - await mineBatchTxs(async () => { - await roninValidatorSet.endEpoch(); - tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + describe('Submit block reward when staking vesting is topped up', async () => { + before(async () => { + await expect(() => stakingVesting.receiveRON({ value: topUpAmount })).changeEtherBalance( + stakingVesting.address, + topUpAmount + ); }); - await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, true); - lastPeriod = await roninValidatorSet.currentPeriod(); - await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, lastPeriod, currentValidatorSet); - await RoninValidatorSet.expects.emitStakingRewardDistributedEvent(tx!, 5049); // (5000 + 100) * 99% - await RoninValidatorSet.expects.emitMiningRewardDistributedEvent(tx!, coinbase.address, treasury.address, 51); // (5000 + 100) * 1% - expect(tx!) - .emit(roninValidatorSet, 'BridgeOperatorRewardDistributed') - .withArgs( + + it('Should be able to submit block reward using coinbase account and receive bonuses', async () => { + const tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + + await RoninValidatorSet.expects.emitBlockRewardSubmittedEvent( + tx, coinbase.address, - bridgeOperator.address, - treasury.address, - BigNumber.from(37).div(await roninValidatorSet.totalBridgeOperators()) + 100, + blockProducerBonusPerBlock ); - const balanceDiff = (await treasury.getBalance()).sub(balance); - expect(balanceDiff).eq(60); // = (5000 + 100) * 1% + 9 = (51 + 9) - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( - 5049 // (5000 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase - ); - }); - it('Should not allocate minting fee for the slashed validators, but allocate bridge reward', async () => { - let tx: ContractTransaction; - { + expect(tx).not.emit(stakingVesting, 'BonusTransferFailed'); + await StakingVestingExpects.emitBonusTransferredEvent( + tx, + undefined, + roninValidatorSet.address, + blockProducerBonusPerBlock, + bridgeOperatorBonusPerBlock + ); + }); + + it('Should be able to get right reward at the end of period', async () => { const balance = await treasury.getBalance(); - await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); - tx = await slashIndicator.slashMisdemeanor(coinbase.address); - expect(tx).emit(roninValidatorSet, 'ValidatorPunished').withArgs(coinbase.address, 0, 0); + let tx: ContractTransaction; + await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); - epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); lastPeriod = await roninValidatorSet.currentPeriod(); + epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); + + await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, true); + lastPeriod = await roninValidatorSet.currentPeriod(); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, lastPeriod, currentValidatorSet); + await RoninValidatorSet.expects.emitStakingRewardDistributedEvent(tx!, 5148); // (5000 + 100 + 100) * 99% + await RoninValidatorSet.expects.emitMiningRewardDistributedEvent(tx!, coinbase.address, treasury.address, 52); // (5000 + 100 + 100) * 1% + expect(tx!) + .emit(roninValidatorSet, 'BridgeOperatorRewardDistributed') + .withArgs( + coinbase.address, + bridgeOperator.address, + treasury.address, + BigNumber.from(37).div(await roninValidatorSet.totalBridgeOperators()) + ); const balanceDiff = (await treasury.getBalance()).sub(balance); - expect(balanceDiff).eq(0); - // The delegators don't receives the new rewards until the period is ended + expect(balanceDiff).eq(61); // = (5000 + 100 + 100) * 1% + 9 = (52 + 9) expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( - 5049 // (5000 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase + 5148 // (5000 + 100 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase ); - await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, false); - expect(tx!).not.emit(roninValidatorSet, 'ValidatorSetUpdated'); - } + }); + + it('Should not allocate minting fee for the slashed validators, but allocate bridge reward', async () => { + let tx: ContractTransaction; + { + const balance = await treasury.getBalance(); + await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + tx = await slashIndicator.slashMisdemeanor(coinbase.address); + expect(tx).emit(roninValidatorSet, 'ValidatorPunished').withArgs(coinbase.address, 0, 0); + + epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); + lastPeriod = await roninValidatorSet.currentPeriod(); + await mineBatchTxs(async () => { + await roninValidatorSet.endEpoch(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); + const balanceDiff = (await treasury.getBalance()).sub(balance); + expect(balanceDiff).eq(0); + // The delegators don't receives the new rewards until the period is ended + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + 5148 // (5000 + 100 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase + ); + await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, false); + expect(tx!).not.emit(roninValidatorSet, 'ValidatorSetUpdated'); + } + + { + const balance = await treasury.getBalance(); + await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); + + epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); + lastPeriod = await roninValidatorSet.currentPeriod(); + await mineBatchTxs(async () => { + await roninValidatorSet.endEpoch(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); + + const balanceDiff = (await treasury.getBalance()).sub(balance); + const totalBridgeReward = bridgeOperatorBonusPerBlock.mul(2); // called submitBlockReward 2 times + expect(balanceDiff).eq(totalBridgeReward.div(await roninValidatorSet.totalBlockProducers())); + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + 5148 // (5000 + 100 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase + ); + await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, true); + lastPeriod = await roninValidatorSet.currentPeriod(); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, lastPeriod, currentValidatorSet); + } + }); - { + it('Should be able to record delegating reward for a successful period', async () => { + let tx: ContractTransaction; const balance = await treasury.getBalance(); await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); + epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); lastPeriod = await roninValidatorSet.currentPeriod(); await mineBatchTxs(async () => { await roninValidatorSet.endEpoch(); tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); }); - const balanceDiff = (await treasury.getBalance()).sub(balance); - const totalBridgeReward = bridgeOperatorBonusPerBlock.mul(2); // called submitBlockReward 2 times - expect(balanceDiff).eq(totalBridgeReward.div(await roninValidatorSet.totalBlockProducers())); - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( - 5049 // (5000 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase - ); await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, true); + lastPeriod = await roninValidatorSet.currentPeriod(); await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, lastPeriod, currentValidatorSet); - } - }); + await RoninValidatorSet.expects.emitStakingRewardDistributedEvent( + tx!, + blockProducerBonusPerBlock.add(100).div(100).mul(99) + ); - it('Should be able to record delegating reward for a successful period', async () => { - let tx: ContractTransaction; - const balance = await treasury.getBalance(); - await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); - await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); - epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); - lastPeriod = await roninValidatorSet.currentPeriod(); - await mineBatchTxs(async () => { - await roninValidatorSet.endEpoch(); - tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + const balanceDiff = (await treasury.getBalance()).sub(balance); + const expectingBalanceDiff = blockProducerBonusPerBlock + .add(100) + .div(100) + .add(bridgeOperatorBonusPerBlock.div(await roninValidatorSet.totalBlockProducers())); + expect(balanceDiff).eq(expectingBalanceDiff); + + let _rewardFromBonus = blockProducerBonusPerBlock.div(100).mul(99).mul(2); + let _rewardFromSubmission = BigNumber.from(100).div(100).mul(99).mul(3); + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + _rewardFromBonus.add(_rewardFromSubmission) + ); }); - await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, true); - lastPeriod = await roninValidatorSet.currentPeriod(); - await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, lastPeriod, currentValidatorSet); - await RoninValidatorSet.expects.emitStakingRewardDistributedEvent( - tx!, - validatorBonusPerBlock.add(100).div(100).mul(99) - ); - const balanceDiff = (await treasury.getBalance()).sub(balance); - const expectingBalanceDiff = validatorBonusPerBlock - .add(100) - .div(100) - .add(bridgeOperatorBonusPerBlock.div(await roninValidatorSet.totalBlockProducers())); - expect(balanceDiff).eq(expectingBalanceDiff); - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( - validatorBonusPerBlock.add(100).div(100).mul(99).mul(2) - ); - }); + it('Should not allocate reward for the slashed validator', async () => { + let tx: ContractTransaction; + const balance = await treasury.getBalance(); + await slashIndicator.slashMisdemeanor(coinbase.address); + tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); + await expect(tx).to.emit(roninValidatorSet, 'BlockRewardRewardDeprecated').withArgs(coinbase.address, 100); + await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); - it('Should not allocate reward for the slashed validator', async () => { - let tx: ContractTransaction; - const balance = await treasury.getBalance(); - await slashIndicator.slashMisdemeanor(coinbase.address); - tx = await roninValidatorSet.connect(coinbase).submitBlockReward({ value: 100 }); - await expect(tx).to.emit(roninValidatorSet, 'BlockRewardRewardDeprecated').withArgs(coinbase.address, 100); - await RoninValidatorSet.EpochController.setTimestampToPeriodEnding(); - epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); - lastPeriod = await roninValidatorSet.currentPeriod(); - await mineBatchTxs(async () => { - await roninValidatorSet.endEpoch(); - tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + epoch = (await roninValidatorSet.epochOf(await ethers.provider.getBlockNumber())).add(1); + lastPeriod = await roninValidatorSet.currentPeriod(); + await mineBatchTxs(async () => { + await roninValidatorSet.endEpoch(); + tx = await roninValidatorSet.connect(coinbase).wrapUpEpoch(); + }); + + const balanceDiff = (await treasury.getBalance()).sub(balance); + expect(balanceDiff).eq(bridgeOperatorBonusPerBlock.div(await roninValidatorSet.totalBlockProducers())); + + let _rewardFromBonus = blockProducerBonusPerBlock.div(100).mul(99).mul(2); + let _rewardFromSubmission = BigNumber.from(100).div(100).mul(99).mul(3); + expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + _rewardFromBonus.add(_rewardFromSubmission) + ); + + await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, true); + lastPeriod = await roninValidatorSet.currentPeriod(); + await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, lastPeriod, currentValidatorSet); }); - const balanceDiff = (await treasury.getBalance()).sub(balance); - expect(balanceDiff).eq(bridgeOperatorBonusPerBlock.div(await roninValidatorSet.totalBlockProducers())); - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( - validatorBonusPerBlock.add(100).div(100).mul(99).mul(2) - ); - await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, true); - lastPeriod = await roninValidatorSet.currentPeriod(); - await RoninValidatorSet.expects.emitValidatorSetUpdatedEvent(tx!, lastPeriod, currentValidatorSet); }); }); }); From 2ef51c71442f36d56f69c8b7e56aa5344d7e6886 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Fri, 4 Nov 2022 11:31:35 +0700 Subject: [PATCH 184/190] Update comment for contracts/interfaces/IMaintenance.sol Co-authored-by: Bao --- contracts/interfaces/IMaintenance.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/interfaces/IMaintenance.sol b/contracts/interfaces/IMaintenance.sol index 99b7c0946..ddfb14653 100644 --- a/contracts/interfaces/IMaintenance.sol +++ b/contracts/interfaces/IMaintenance.sol @@ -9,8 +9,8 @@ interface IMaintenance { uint256 lastUpdatedBlock; } - /// @dev Emitted when the maintenance is scheduled. - event MaintenanceScheduled(address consensusAddr, Schedule); + /// @dev Emitted when a maintenance is scheduled. + event MaintenanceScheduled(address indexed consensusAddr, Schedule); /// @dev Emitted when the maintenance config is updated. event MaintenanceConfigUpdated( uint256 minMaintenanceBlockPeriod, From 4f089cbfd6e777e8819cc33a7f6e4ac52a9f9adc Mon Sep 17 00:00:00 2001 From: Bao Date: Fri, 4 Nov 2022 14:02:05 +0700 Subject: [PATCH 185/190] Fix typo in pull request template (#55) --- pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pull_request_template.md b/pull_request_template.md index d7b466d73..94c05ca0a 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,6 +1,6 @@ ### Description ### Checklist -- [ ] I have clearly commented on all the main functions followed the [NatSpec Format](https://docs.soliditylang.org/en/v0.8.0/natspec-format.html) +- [ ] I have clearly commented on all the main functions following the [NatSpec Format](https://docs.soliditylang.org/en/v0.8.0/natspec-format.html) - [ ] The box that allows repo maintainers to update this PR is checked - [ ] I tested locally to make sure this feature/fix works From b65905e0d5a91abe0c5116842ed63a38be1e1230 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Fri, 4 Nov 2022 14:14:45 +0700 Subject: [PATCH 186/190] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 18480700a..a7fcda154 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ The next version of smart contracts that power Ronin DPoS network. -_NOTE: This smart contract version does not includes method to prevent the 51% attack. It is not finalized at the implementation time._ - [Overview](#ronin-dpos-contracts) - [Staking contract](#staking-contract) From a11290d0c04a498868bec833358fc5c2390e9200 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Fri, 4 Nov 2022 14:20:58 +0700 Subject: [PATCH 187/190] Update contracts/ronin/staking/StakingManager.sol --- contracts/ronin/staking/StakingManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/ronin/staking/StakingManager.sol b/contracts/ronin/staking/StakingManager.sol index 55a276bd5..b9de8b4c0 100644 --- a/contracts/ronin/staking/StakingManager.sol +++ b/contracts/ronin/staking/StakingManager.sol @@ -155,7 +155,7 @@ abstract contract StakingManager is } /** - * @dev Proposes a candidate to become a valdiator. + * @dev Proposes a candidate to become a validator. * * Requirements: * - The pool admin is able to receive RON. From 9ef4972a40a454ab2225e92001ce38df383a49d2 Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Fri, 4 Nov 2022 14:30:03 +0700 Subject: [PATCH 188/190] [Staking] Update reward calculation mechanism (#44) * Update reward calculation mechanism * Change balance to stakingAmount * Fix rebase issues * Update pool shares syncing mechanism --- contracts/interfaces/IRewardPool.sol | 84 +-- contracts/interfaces/IStaking.sol | 80 +-- .../consumers/PeriodWrapperConsumer.sol | 11 + contracts/mocks/MockPrecompile.sol | 4 +- ...ockRoninValidatorSetOverridePrecompile.sol | 4 +- contracts/mocks/MockStaking.sol | 83 +-- contracts/mocks/MockValidatorSet.sol | 23 +- .../MockPrecompileUsagePickValidatorSet.sol | 4 +- .../PrecompileUsagePickValidatorSet.sol | 4 +- contracts/ronin/staking/RewardCalculation.sol | 260 ++++---- contracts/ronin/staking/Staking.sol | 94 +-- contracts/ronin/staking/StakingManager.sol | 88 ++- .../ronin/validator/CandidateManager.sol | 35 +- .../ronin/validator/RoninValidatorSet.sol | 63 +- src/config.ts | 3 +- src/deploy/proxy/staking-proxy.ts | 2 +- src/utils.ts | 2 +- test/governance-admin/GovernanceAdmin.test.ts | 4 +- test/helpers/fixture.ts | 2 +- test/helpers/reward-calculation.ts | 30 - test/helpers/staking.ts | 20 +- .../integration/ActionSlashValidators.test.ts | 26 +- test/integration/ActionSubmitReward.test.ts | 13 +- test/integration/ActionWrapUpEpoch.test.ts | 14 +- test/integration/Configuration.test.ts | 4 +- test/maintainance/Maintenance.test.ts | 6 +- test/slash/CreditScore.test.ts | 6 +- test/slash/SlashIndicator.test.ts | 6 +- test/staking/AdvancedCalculation.test.ts | 586 ------------------ test/staking/RewardAmountCalculation.test.ts | 327 ---------- test/staking/RewardCalculation.test.ts | 221 +++++++ test/staking/Staking.test.ts | 55 +- test/validator/RoninValidatorSet.test.ts | 20 +- 33 files changed, 702 insertions(+), 1482 deletions(-) create mode 100644 contracts/interfaces/consumers/PeriodWrapperConsumer.sol delete mode 100644 test/helpers/reward-calculation.ts delete mode 100644 test/staking/AdvancedCalculation.test.ts delete mode 100644 test/staking/RewardAmountCalculation.test.ts create mode 100644 test/staking/RewardCalculation.test.ts diff --git a/contracts/interfaces/IRewardPool.sol b/contracts/interfaces/IRewardPool.sol index d0196601c..4c36ba3dd 100644 --- a/contracts/interfaces/IRewardPool.sol +++ b/contracts/interfaces/IRewardPool.sol @@ -2,84 +2,66 @@ pragma solidity ^0.8.9; -interface IRewardPool { - /// @dev Emitted when the settled pool is updated. - event SettledPoolsUpdated(address[] poolAddress, uint256[] accumulatedRps); - /// @dev Emitted when the pending pool is updated. - event PendingPoolUpdated(address poolAddress, uint256 accumulatedRps); - /// @dev Emitted when the fields to calculate settled reward for the user is updated. - event SettledRewardUpdated(address poolAddress, address user, uint256 debited, uint256 accumulatedRps); +import "../interfaces/consumers/PeriodWrapperConsumer.sol"; + +interface IRewardPool is PeriodWrapperConsumer { /// @dev Emitted when the fields to calculate pending reward for the user is updated. - event PendingRewardUpdated(address poolAddress, address user, uint256 debited, uint256 credited); + event UserRewardUpdated(address indexed poolAddr, address indexed user, uint256 debited); /// @dev Emitted when the user claimed their reward - event RewardClaimed(address poolAddress, address user, uint256 amount); + event RewardClaimed(address indexed poolAddr, address indexed user, uint256 amount); - struct PendingRewardFields { - // Recorded reward amount. - uint256 debited; - // The amount rewards that user have already earned. - uint256 credited; - // Last period number that the info updated. - uint256 lastSyncedPeriod; - } + /// @dev Emitted when the pool shares are updated + event PoolSharesUpdated(uint256 indexed period, address indexed poolAddr, uint256 shares); + /// @dev Emitted when the pools are updated + event PoolsUpdated(uint256 indexed period, address[] poolAddrs, uint256[] aRps, uint256[] shares); + /// @dev Emitted when the contract fails when updating the pools + event PoolsUpdateFailed(uint256 indexed period, address[] poolAddrs, uint256[] rewards); + /// @dev Emitted when the contract fails when updating a pool that already set + event PoolUpdateConflicted(uint256 indexed period, address indexed poolAddr); - struct SettledRewardFields { + struct UserRewardFields { // Recorded reward amount. uint256 debited; - // Accumulated of the amount rewards per share (one unit staking). - uint256 accumulatedRps; - } - - struct PendingPool { - // Accumulated of the amount rewards per share (one unit staking). - uint256 accumulatedRps; + // The last accumulated of the amount rewards per share (one unit staking) that the info updated. + uint256 aRps; + // Min staking amount in the period. + uint256 minAmount; + // Last period number that the info updated. + uint256 lastPeriod; } - struct SettledPool { - // Last period number that the info updated. - uint256 lastSyncedPeriod; + struct PoolFields { // Accumulated of the amount rewards per share (one unit staking). - uint256 accumulatedRps; + uint256 aRps; + // The staking total to share reward of the current period. + PeriodWrapper shares; } - /** - * @dev Returns total rewards from scratch including pending reward and claimable reward except the claimed amount. - * - * Note: Do not use this function to get claimable reward, consider using the method `getClaimableReward` instead. - * - */ - function getTotalReward(address _poolAddr, address _user) external view returns (uint256); - /** * @dev Returns the reward amount that user claimable. */ - function getClaimableReward(address _poolAddr, address _user) external view returns (uint256); - - /** - * @dev Returns the pending reward. - */ - function getPendingReward(address _poolAddr, address _user) external view returns (uint256 _amount); + function getReward(address _poolAddr, address _user) external view returns (uint256); /** - * @dev Returns the staked amount of the user. + * @dev Returns the staking amount of an user. */ - function balanceOf(address _poolAddr, address _user) external view returns (uint256); + function stakingAmountOf(address _poolAddr, address _user) external view returns (uint256); /** - * @dev Returns the staked amounts of the users. + * @dev Returns the staking amounts of the users. */ - function bulkBalanceOf(address[] calldata _poolAddrs, address[] calldata _userList) + function bulkStakingAmountOf(address[] calldata _poolAddrs, address[] calldata _userList) external view returns (uint256[] memory); /** - * @dev Returns the total staked amount of all users. + * @dev Returns the total staking amount of all users for a pool. */ - function totalBalance(address _poolAddr) external view returns (uint256); + function stakingTotal(address _poolAddr) external view returns (uint256); /** - * @dev Returns the total staked amount of all users. + * @dev Returns the total staking amounts of all users for the pools `_poolAddrs`. */ - function totalBalances(address[] calldata _poolAddr) external view returns (uint256[] memory); + function bulkStakingTotal(address[] calldata _poolAddrs) external view returns (uint256[] memory); } diff --git a/contracts/interfaces/IStaking.sol b/contracts/interfaces/IStaking.sol index 69e0eccfd..cc90c9d87 100644 --- a/contracts/interfaces/IStaking.sol +++ b/contracts/interfaces/IStaking.sol @@ -10,20 +10,20 @@ interface IStaking is IRewardPool { address addr; // Pool admin address address admin; - // Self-staked amount - uint256 stakedAmount; - // Total balance of the pool - uint256 totalBalance; - // Mapping from delegator => delegated amount - mapping(address => uint256) delegatedAmount; + // Self-staking amount + uint256 stakingAmount; + // Total number of RON staking for the pool + uint256 stakingTotal; + // Mapping from delegator => delegating amount + mapping(address => uint256) delegatingAmount; } /// @dev Emitted when the validator pool is approved. event PoolApproved(address indexed validator, address indexed admin); /// @dev Emitted when the validator pool is deprecated. event PoolsDeprecated(address[] validator); - /// @dev Emitted when the staked amount is deprecated. - event StakedAmountDeprecated(address indexed validator, address indexed admin, uint256 amount); + /// @dev Emitted when the staking amount is deprecated. + event StakingAmountDeprecated(address indexed validator, address indexed admin, uint256 amount); /// @dev Emitted when the pool admin staked for themself. event Staked(address indexed consensuAddr, uint256 amount); /// @dev Emitted when the pool admin unstaked the amount of RON from themself. @@ -32,8 +32,8 @@ interface IStaking is IRewardPool { event Delegated(address indexed delegator, address indexed consensuAddr, uint256 amount); /// @dev Emitted when the delegator unstaked from a validator candidate. event Undelegated(address indexed delegator, address indexed consensuAddr, uint256 amount); - /// @dev Emitted when the minimum balance for being a validator is updated. - event MinValidatorBalanceUpdated(uint256 threshold); + /// @dev Emitted when the minimum staking amount for being a validator is updated. + event MinValidatorStakingAmountUpdated(uint256 threshold); /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR GOVERNANCE // @@ -42,7 +42,7 @@ interface IStaking is IRewardPool { /** * @dev Returns the minimum threshold for being a validator candidate. */ - function minValidatorBalance() external view returns (uint256); + function minValidatorStakingAmount() external view returns (uint256); /** * @dev Sets the minimum threshold for being a validator candidate. @@ -50,52 +50,36 @@ interface IStaking is IRewardPool { * Requirements: * - The method caller is admin. * - * Emits the `MinValidatorBalanceUpdated` event. + * Emits the `MinValidatorStakingAmountUpdated` event. * */ - function setMinValidatorBalance(uint256) external; + function setMinValidatorStakingAmount(uint256) external; /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR VALIDATOR CONTRACT // /////////////////////////////////////////////////////////////////////////////////////// /** - * @dev Records the amount of reward `_reward` for the pending pool `_poolAddr`. + * @dev Records the amount of rewards `_rewards` for the pools `_poolAddrs`. * * Requirements: * - The method caller is validator contract. * - * Emits the `PendingPoolUpdated` event. + * Emits the event `PoolsUpdated` once the contract recorded the rewards successfully. + * Emits the event `PoolsUpdateFailed` once the input array lengths are not equal. + * Emits the event `PoolUpdateConflicted` when the pool is already updated in the period. * - * Note: This method should not be called after the pending pool is sinked. + * Note: This method should be called once at the period ending. * */ - function recordReward(address _consensusAddr, uint256 _reward) external payable; - - /** - * @dev Settles the pending pool and allocates rewards for the pool `_consensusAddr`. - * - * Requirements: - * - The method caller is validator contract. - * - * Emits the `SettledPoolsUpdated` event. - * - */ - function settleRewardPools(address[] calldata _consensusAddrs) external; - - /** - * @dev Handles when the pending reward pool of the validator is sinked. - * - * Requirements: - * - The method caller is validator contract. - * - * Emits the `PendingPoolUpdated` event. - * - */ - function sinkPendingReward(address _consensusAddr) external; + function recordRewards( + uint256 _period, + address[] calldata _consensusAddrs, + uint256[] calldata _rewards + ) external payable; /** - * @dev Deducts from staked amount of the validator `_consensusAddr` for `_amount`. + * @dev Deducts from staking amount of the validator `_consensusAddr` for `_amount`. * * Requirements: * - The method caller is validator contract. @@ -103,7 +87,7 @@ interface IStaking is IRewardPool { * Emits the event `Unstaked`. * */ - function deductStakedAmount(address _consensusAddr, uint256 _amount) external; + function deductStakingAmount(address _consensusAddr, uint256 _amount) external; /** * @dev Deprecates the pool. @@ -112,7 +96,7 @@ interface IStaking is IRewardPool { * - The method caller is validator contract. * * Emits the event `PoolsDeprecated` and `Unstaked` events. - * Emits the event `StakedAmountDeprecated` if the contract cannot transfer RON back to the pool admin. + * Emits the event `StakingAmountDeprecated` if the contract cannot transfer RON back to the pool admin. * */ function deprecatePools(address[] calldata _pools) external; @@ -127,7 +111,7 @@ interface IStaking is IRewardPool { * Requirements: * - The method caller is able to receive RON. * - The treasury is able to receive RON. - * - The amount is larger than or equal to the minimum validator balance `minValidatorBalance()`. + * - The amount is larger than or equal to the minimum validator staking amount `minValidatorStakingAmount()`. * * Emits the event `PoolApproved`. * @@ -169,7 +153,7 @@ interface IStaking is IRewardPool { function unstake(address _consensusAddr, uint256 _amount) external; /** - * @dev Renounces being a validator candidate and takes back the delegated/staked amount. + * @dev Renounces being a validator candidate and takes back the delegating/staking amount. * * Requirements: * - The consensus address is a validator candidate. @@ -233,12 +217,12 @@ interface IStaking is IRewardPool { ) external; /** - * @dev Returns the pending reward and the claimable reward of the user `_user`. + * @dev Returns the claimable reward of the user `_user`. */ function getRewards(address _user, address[] calldata _poolAddrList) external view - returns (uint256[] memory _pendings, uint256[] memory _claimables); + returns (uint256[] memory _rewards); /** * @dev Claims the reward of method caller. @@ -270,7 +254,7 @@ interface IStaking is IRewardPool { view returns ( address _admin, - uint256 _stakedAmount, - uint256 _totalBalance + uint256 _stakingAmount, + uint256 _stakingTotal ); } diff --git a/contracts/interfaces/consumers/PeriodWrapperConsumer.sol b/contracts/interfaces/consumers/PeriodWrapperConsumer.sol new file mode 100644 index 000000000..1f8b5d6f6 --- /dev/null +++ b/contracts/interfaces/consumers/PeriodWrapperConsumer.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface PeriodWrapperConsumer { + struct PeriodWrapper { + // Inner value. + uint256 inner; + // Last period number that the info updated. + uint256 lastPeriod; + } +} diff --git a/contracts/mocks/MockPrecompile.sol b/contracts/mocks/MockPrecompile.sol index 8932be402..1fe464e5e 100644 --- a/contracts/mocks/MockPrecompile.sol +++ b/contracts/mocks/MockPrecompile.sol @@ -23,12 +23,12 @@ contract MockPrecompile { function pickValidatorSet( address[] memory _candidates, - uint256[] memory _balanceWeights, + uint256[] memory _weights, uint256[] memory _trustedWeights, uint256 _maxValidatorNumber, uint256 _maxPrioritizedValidatorNumber ) public pure returns (address[] memory _result) { - (_result, _trustedWeights) = Sorting.sortWithExternalKeys(_candidates, _balanceWeights, _trustedWeights); + (_result, _trustedWeights) = Sorting.sortWithExternalKeys(_candidates, _weights, _trustedWeights); uint256 _newValidatorCount = Math.min(_maxValidatorNumber, _result.length); _arrangeValidatorCandidates(_result, _trustedWeights, _newValidatorCount, _maxPrioritizedValidatorNumber); } diff --git a/contracts/mocks/MockRoninValidatorSetOverridePrecompile.sol b/contracts/mocks/MockRoninValidatorSetOverridePrecompile.sol index 4c34bc092..11a3af629 100644 --- a/contracts/mocks/MockRoninValidatorSetOverridePrecompile.sol +++ b/contracts/mocks/MockRoninValidatorSetOverridePrecompile.sol @@ -30,14 +30,14 @@ contract MockRoninValidatorSetOverridePrecompile is RoninValidatorSet, MockPreco function _pcPickValidatorSet( address[] memory _candidates, - uint256[] memory _balanceWeights, + uint256[] memory _weights, uint256[] memory _trustedWeights, uint256 _maxValidatorNumber, uint256 _maxPrioritizedValidatorNumber ) internal pure override returns (address[] memory _result, uint256 _newValidatorCount) { _result = pickValidatorSet( _candidates, - _balanceWeights, + _weights, _trustedWeights, _maxValidatorNumber, _maxPrioritizedValidatorNumber diff --git a/contracts/mocks/MockStaking.sol b/contracts/mocks/MockStaking.sol index bd74aa05e..e6afa5471 100644 --- a/contracts/mocks/MockStaking.sol +++ b/contracts/mocks/MockStaking.sol @@ -6,55 +6,66 @@ import "../ronin/staking/RewardCalculation.sol"; contract MockStaking is RewardCalculation { /// @dev Mapping from user => staking balance - mapping(address => uint256) internal _stakingBalance; + mapping(address => uint256) internal _stakingAmount; /// @dev Mapping from period number => slashed mapping(uint256 => bool) internal _periodSlashed; - uint256 internal _lastUpdatedPeriod; - uint256 internal _totalBalance; + uint256 internal _stakingTotal; + + uint256 public lastUpdatedPeriod; + uint256 public pendingReward; address public poolAddr; constructor(address _poolAddr) { - _lastUpdatedPeriod++; poolAddr = _poolAddr; } + function firstEverWrapup() external { + delete pendingReward; + lastUpdatedPeriod = block.timestamp / 1 days + 1; + } + function endPeriod() external { - _lastUpdatedPeriod++; + address[] memory _addrs = new address[](1); + uint256[] memory _rewards = new uint256[](1); + _addrs[0] = poolAddr; + _rewards[0] = pendingReward; + this.recordRewards(_addrs, _rewards); + + pendingReward = 0; + lastUpdatedPeriod++; } - function stake(address _user, uint256 _amount) external { - uint256 _balance = _stakingBalance[_user]; - uint256 _newBalance = _balance + _amount; - _syncUserReward(poolAddr, _user, _newBalance); - _stakingBalance[_user] = _newBalance; - _totalBalance += _amount; + function increasePeriod() external { + lastUpdatedPeriod++; } - function unstake(address _user, uint256 _amount) external { - uint256 _balance = _stakingBalance[_user]; - uint256 _newBalance = _balance - _amount; - _syncUserReward(poolAddr, _user, _newBalance); - _stakingBalance[_user] = _newBalance; - _totalBalance -= _amount; + function stake(address _user, uint256 _amount) external { + uint256 _lastStakingAmount = _stakingAmount[_user]; + uint256 _newStakingAmount = _lastStakingAmount + _amount; + _syncUserReward(poolAddr, _user, _newStakingAmount); + _stakingAmount[_user] = _newStakingAmount; + _stakingTotal += _amount; } - function slash() external { - uint256 _period = getPeriod(); - _periodSlashed[_period] = true; - _sinkPendingReward(poolAddr); + function unstake(address _user, uint256 _amount) external { + uint256 _lastStakingAmount = _stakingAmount[_user]; + uint256 _newStakingAmount = _lastStakingAmount - _amount; + _syncUserReward(poolAddr, _user, _newStakingAmount); + _stakingAmount[_user] = _newStakingAmount; + _stakingTotal -= _amount; } - function recordReward(uint256 _rewardAmount) external { - _recordReward(poolAddr, _rewardAmount); + function increaseReward(uint256 _amount) external { + pendingReward += _amount; } - function settledPools(address[] calldata _addrList) external { - _onPoolsSettled(_addrList); + function decreaseReward(uint256 _amount) external { + pendingReward -= _amount; } - function increaseAccumulatedRps(uint256 _amount) external { - _recordReward(poolAddr, _amount); + function recordRewards(address[] calldata _addrList, uint256[] calldata _rewards) external { + _recordRewards(_currentPeriod(), _addrList, _rewards); } function getPeriod() public view returns (uint256) { @@ -65,28 +76,24 @@ contract MockStaking is RewardCalculation { _amount = _claimReward(poolAddr, _user); } - function balanceOf(address, address _user) public view override returns (uint256) { - return _stakingBalance[_user]; + function stakingAmountOf(address, address _user) public view override returns (uint256) { + return _stakingAmount[_user]; } - function bulkBalanceOf(address[] calldata _poolAddrs, address[] calldata _userList) + function bulkStakingAmountOf(address[] calldata _poolAddrs, address[] calldata _userList) external view override returns (uint256[] memory) {} - function totalBalance(address) public view virtual override returns (uint256) { - return _totalBalance; - } - - function _rewardSinked(address, uint256 _period) internal view override returns (bool) { - return _periodSlashed[_period]; + function stakingTotal(address) public view virtual override returns (uint256) { + return _stakingTotal; } function _currentPeriod() internal view override returns (uint256 _period) { - return _lastUpdatedPeriod; + return lastUpdatedPeriod; } - function totalBalances(address[] calldata _poolAddr) external view override returns (uint256[] memory) {} + function bulkStakingTotal(address[] calldata _poolAddr) external view override returns (uint256[] memory) {} } diff --git a/contracts/mocks/MockValidatorSet.sol b/contracts/mocks/MockValidatorSet.sol index 8b30f07c5..6a98ef43c 100644 --- a/contracts/mocks/MockValidatorSet.sol +++ b/contracts/mocks/MockValidatorSet.sol @@ -30,31 +30,10 @@ contract MockValidatorSet is IRoninValidatorSet, CandidateManager { _numberOfBlocksInEpoch = __numberOfBlocksInEpoch; } - function depositReward() external payable { - _stakingContract.recordReward{ value: msg.value }(msg.sender, msg.value); - } - - function settledReward(address[] calldata _validatorList) external { - _stakingContract.settleRewardPools(_validatorList); - } - - function slashMisdemeanor(address _validator) external { - _stakingContract.sinkPendingReward(_validator); - } - - function slashFelony(address _validator) external { - _stakingContract.sinkPendingReward(_validator); - _stakingContract.deductStakedAmount(_validator, 1); - } - - function slashDoubleSign(address _validator) external { - _stakingContract.sinkPendingReward(_validator); - } - function submitBlockReward() external payable override {} function wrapUpEpoch() external payable override { - _filterUnsatisfiedCandidates(0); + _filterUnsatisfiedCandidates(); _lastUpdatedPeriod = currentPeriod(); } diff --git a/contracts/mocks/precompile-usages/MockPrecompileUsagePickValidatorSet.sol b/contracts/mocks/precompile-usages/MockPrecompileUsagePickValidatorSet.sol index 8b742ed18..bd2e47108 100644 --- a/contracts/mocks/precompile-usages/MockPrecompileUsagePickValidatorSet.sol +++ b/contracts/mocks/precompile-usages/MockPrecompileUsagePickValidatorSet.sol @@ -21,14 +21,14 @@ contract MockPrecompileUsagePickValidatorSet is PrecompileUsagePickValidatorSet function callPrecompile( address[] memory _candidates, - uint256[] memory _balanceWeights, + uint256[] memory _weights, uint256[] memory _trustedWeights, uint256 _maxValidatorNumber, uint256 _maxPrioritizedValidatorNumber ) public view returns (address[] memory _result) { (_result, ) = _pcPickValidatorSet( _candidates, - _balanceWeights, + _weights, _trustedWeights, _maxValidatorNumber, _maxPrioritizedValidatorNumber diff --git a/contracts/precompile-usages/PrecompileUsagePickValidatorSet.sol b/contracts/precompile-usages/PrecompileUsagePickValidatorSet.sol index 4b87a96ce..bee39ff08 100644 --- a/contracts/precompile-usages/PrecompileUsagePickValidatorSet.sol +++ b/contracts/precompile-usages/PrecompileUsagePickValidatorSet.sol @@ -16,7 +16,7 @@ abstract contract PrecompileUsagePickValidatorSet { */ function _pcPickValidatorSet( address[] memory _candidates, - uint256[] memory _balanceWeights, + uint256[] memory _weights, uint256[] memory _trustedWeights, uint256 _maxValidatorNumber, uint256 _maxPrioritizedValidatorNumber @@ -25,7 +25,7 @@ abstract contract PrecompileUsagePickValidatorSet { bytes memory _payload = abi.encodeWithSignature( "pickValidatorSet(address[],uint256[],uint256[],uint256,uint256)", _candidates, - _balanceWeights, + _weights, _trustedWeights, _maxValidatorNumber, _maxPrioritizedValidatorNumber diff --git a/contracts/ronin/staking/RewardCalculation.sol b/contracts/ronin/staking/RewardCalculation.sol index 2a3f6557d..c32e0c659 100644 --- a/contracts/ronin/staking/RewardCalculation.sol +++ b/contracts/ronin/staking/RewardCalculation.sol @@ -3,200 +3,200 @@ pragma solidity ^0.8.9; import "../../interfaces/IRewardPool.sol"; +import "../../libraries/Math.sol"; /** * @title RewardCalculation contract * @dev This contract mainly contains the methods to calculate reward for staking contract. */ abstract contract RewardCalculation is IRewardPool { - /// @dev Mapping from the pool address => user address => settled reward info of the user - mapping(address => mapping(address => SettledRewardFields)) internal _sUserReward; - /// @dev Mapping from the pool address => user address => pending reward info of the user - mapping(address => mapping(address => PendingRewardFields)) internal _pUserReward; - - /// @dev Mapping from the pool address => pending pool data - mapping(address => PendingPool) internal _pendingPool; - /// @dev Mapping from the pool address => settled pool data - mapping(address => SettledPool) internal _settledPool; + /// @dev Mapping from period number => accumulated rewards per share (one unit staking) + mapping(uint256 => PeriodWrapper) private _accumulatedRps; + /// @dev Mapping from the pool address => user address => the reward info of the user + mapping(address => mapping(address => UserRewardFields)) private _userReward; + /// @dev Mapping from the pool address => reward pool fields + mapping(address => PoolFields) private _stakingPool; /** * @inheritdoc IRewardPool */ - function getTotalReward(address _poolAddr, address _user) public view returns (uint256) { - PendingRewardFields memory _reward = _pUserReward[_poolAddr][_user]; - PendingPool memory _pool = _pendingPool[_poolAddr]; - - uint256 _balance = balanceOf(_poolAddr, _user); - if (_rewardSinked(_poolAddr, _reward.lastSyncedPeriod)) { - SettledRewardFields memory _sReward = _sUserReward[_poolAddr][_user]; - uint256 _diffRps = _pool.accumulatedRps - _sReward.accumulatedRps; - return (_balance * _diffRps) / 1e18 + _sReward.debited; - } - - return (_balance * _pool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; + function getReward(address _poolAddr, address _user) external view returns (uint256) { + return _getReward(_poolAddr, _user, _currentPeriod(), stakingAmountOf(_poolAddr, _user)); } /** * @inheritdoc IRewardPool */ - function getClaimableReward(address _poolAddr, address _user) public view returns (uint256) { - PendingRewardFields memory _reward = _pUserReward[_poolAddr][_user]; - SettledRewardFields memory _sReward = _sUserReward[_poolAddr][_user]; - SettledPool memory _sPool = _settledPool[_poolAddr]; - - uint256 _diffRps = _sPool.accumulatedRps - _sReward.accumulatedRps; - if (_reward.lastSyncedPeriod <= _sPool.lastSyncedPeriod) { - uint256 _currentBalance = balanceOf(_poolAddr, _user); - - if (_rewardSinked(_poolAddr, _reward.lastSyncedPeriod)) { - return (_currentBalance * _diffRps) / 1e18 + _sReward.debited; - } - - return (_currentBalance * _sPool.accumulatedRps) / 1e18 + _reward.debited - _reward.credited; - } - - return _sReward.debited; - } + function stakingAmountOf(address _poolAddr, address _user) public view virtual returns (uint256); /** * @inheritdoc IRewardPool */ - function getPendingReward(address _poolAddr, address _user) external view returns (uint256 _amount) { - _amount = getTotalReward(_poolAddr, _user) - getClaimableReward(_poolAddr, _user); - } + function stakingTotal(address _poolAddr) public view virtual returns (uint256); /** - * @inheritdoc IRewardPool + * @dev Returns the reward amount that user claimable. */ - function balanceOf(address _poolAddr, address _user) public view virtual returns (uint256); + function _getReward( + address _poolAddr, + address _user, + uint256 _latestPeriod, + uint256 _latestStakingAmount + ) internal view returns (uint256) { + UserRewardFields storage _reward = _userReward[_poolAddr][_user]; - /** - * @inheritdoc IRewardPool - */ - function totalBalance(address _poolAddr) public view virtual returns (uint256); + if (_reward.lastPeriod == _latestPeriod) { + return _reward.debited; + } + + PoolFields storage _pool = _stakingPool[_poolAddr]; + uint256 _minAmount = _reward.minAmount; + uint256 _aRps = _accumulatedRps[_reward.lastPeriod].inner; + uint256 _lastPeriodReward = _minAmount * (_aRps - _reward.aRps); + uint256 _newPeriodsReward = _latestStakingAmount * (_pool.aRps - _aRps); + return _reward.debited + (_lastPeriodReward + _newPeriodsReward) / 1e18; + } /** * @dev Syncs the user reward. * - * Emits the `SettledRewardUpdated` event if the last block user made changes is recorded in the settled period. - * Emits the `PendingRewardUpdated` event. + * Emits the event `UserRewardUpdated` once the debit amount is updated. + * Emits the event `PoolSharesUpdated` once the pool share is updated. * - * Note: The method should be called whenever the user's balance changes. + * Note: The method should be called whenever the user's staking amount changes. * */ function _syncUserReward( address _poolAddr, address _user, - uint256 _newBalance + uint256 _newStakingAmount ) internal { - PendingRewardFields storage _reward = _pUserReward[_poolAddr][_user]; - SettledPool memory _sPool = _settledPool[_poolAddr]; + uint256 _period = _currentPeriod(); + PoolFields storage _pool = _stakingPool[_poolAddr]; + uint256 _lastShares = _pool.shares.inner; + + // Updates the pool shares if it is outdated + if (_pool.shares.lastPeriod < _period) { + _pool.shares = PeriodWrapper(stakingTotal(_poolAddr), _period); + } - // Syncs the reward once the last sync is settled. - if (_reward.lastSyncedPeriod <= _sPool.lastSyncedPeriod) { - uint256 _claimableReward = getClaimableReward(_poolAddr, _user); + UserRewardFields storage _reward = _userReward[_poolAddr][_user]; + uint256 _currentStakingAmount = stakingAmountOf(_poolAddr, _user); + uint256 _debited = _getReward(_poolAddr, _user, _period, _currentStakingAmount); - SettledRewardFields storage _sReward = _sUserReward[_poolAddr][_user]; - _sReward.debited = _claimableReward; - _sReward.accumulatedRps = _sPool.accumulatedRps; - emit SettledRewardUpdated(_poolAddr, _user, _claimableReward, _sPool.accumulatedRps); + if (_reward.debited != _debited) { + _reward.debited = _debited; + emit UserRewardUpdated(_poolAddr, _user, _debited); } - PendingPool memory _pool = _pendingPool[_poolAddr]; - uint256 _debited = getTotalReward(_poolAddr, _user); - uint256 _credited = (_newBalance * _pool.accumulatedRps) / 1e18; + _syncMinStakingAmount(_pool, _reward, _period, _newStakingAmount, _currentStakingAmount); + _reward.aRps = _pool.aRps; + _reward.lastPeriod = _period; - _reward.debited = _debited; - _reward.credited = _credited; - _reward.lastSyncedPeriod = _currentPeriod(); - emit PendingRewardUpdated(_poolAddr, _user, _debited, _credited); + if (_pool.shares.inner != _lastShares) { + emit PoolSharesUpdated(_period, _poolAddr, _pool.shares.inner); + } } /** - * @dev Claims the settled reward for a specific user. - * - * Emits the `PendingRewardUpdated` event and the `SettledRewardUpdated` event. - * - * Note: This method should be called before transferring rewards for the user. - * + * @dev Syncs the minimum staking amount of an user in the current period. */ - function _claimReward(address _poolAddr, address _user) internal returns (uint256 _amount) { - _amount = getClaimableReward(_poolAddr, _user); - emit RewardClaimed(_poolAddr, _user, _amount); - - SettledPool memory _sPool = _settledPool[_poolAddr]; - PendingRewardFields storage _reward = _pUserReward[_poolAddr][_user]; - SettledRewardFields storage _sReward = _sUserReward[_poolAddr][_user]; - - _sReward.debited = 0; - if (_reward.lastSyncedPeriod <= _sPool.lastSyncedPeriod) { - _sReward.accumulatedRps = _sPool.accumulatedRps; + function _syncMinStakingAmount( + PoolFields storage _pool, + UserRewardFields storage _reward, + uint256 _latestPeriod, + uint256 _newStakingAmount, + uint256 _currentStakingAmount + ) internal { + if (_reward.lastPeriod < _latestPeriod) { + _reward.minAmount = _currentStakingAmount; } - emit SettledRewardUpdated(_poolAddr, _user, 0, _sReward.accumulatedRps); - _reward.credited += _amount; - _reward.lastSyncedPeriod = _currentPeriod(); - emit PendingRewardUpdated(_poolAddr, _user, _reward.debited, _reward.credited); + uint256 _minAmount = Math.min(_reward.minAmount, _newStakingAmount); + uint256 _diffAmount = _reward.minAmount - _minAmount; + if (_diffAmount > 0) { + _reward.minAmount = _minAmount; + require(_pool.shares.inner >= _diffAmount, "RewardCalculation: invalid pool shares"); + _pool.shares.inner -= _diffAmount; + } } /** - * @dev Records the amount of reward `_reward` for the pending pool `_poolAddr`. + * @dev Claims the settled reward for a specific user. * - * Emits the `PendingPoolUpdated` event. + * Emits the `PendingRewardUpdated` event and the `SettledRewardUpdated` event. * - * Note: This method should not be called after the pending pool is sinked. + * Note: This method should be called before transferring rewards for the user. * */ - function _recordReward(address _poolAddr, uint256 _reward) internal { - PendingPool storage _pool = _pendingPool[_poolAddr]; - uint256 _accumulatedRps = _pool.accumulatedRps + (_reward * 1e18) / totalBalance(_poolAddr); - _pool.accumulatedRps = _accumulatedRps; - emit PendingPoolUpdated(_poolAddr, _accumulatedRps); - } + function _claimReward(address _poolAddr, address _user) internal returns (uint256 _amount) { + uint256 _latestPeriod = _currentPeriod(); + _amount = _getReward(_poolAddr, _user, _latestPeriod, stakingAmountOf(_poolAddr, _user)); + emit RewardClaimed(_poolAddr, _user, _amount); - /** - * @dev Handles when the pool `_poolAddr` is sinked. - * - * Emits the `PendingPoolUpdated` event. - * - * Note: This method should be called when the pool is sinked. - * - */ - function _sinkPendingReward(address _poolAddr) internal { - uint256 _accumulatedRps = _settledPool[_poolAddr].accumulatedRps; - PendingPool storage _pool = _pendingPool[_poolAddr]; - _pool.accumulatedRps = _accumulatedRps; - emit PendingPoolUpdated(_poolAddr, _accumulatedRps); + UserRewardFields storage _reward = _userReward[_poolAddr][_user]; + _reward.debited = 0; + _reward.lastPeriod = _latestPeriod; + _reward.aRps = _stakingPool[_poolAddr].aRps; + emit UserRewardUpdated(_poolAddr, _user, 0); } /** - * @dev Handles when the pool `_poolAddr` is settled. + * @dev Records the amount of rewards `_rewards` for the pools `_poolAddrs`. * - * Emits the `SettledPoolsUpdated` event. + * Emits the event `PoolsUpdated` once the contract recorded the rewards successfully. + * Emits the event `PoolsUpdateFailed` once the input array lengths are not equal. + * Emits the event `PoolUpdateConflicted` when the pool is already updated in the period. * - * Note: This method should be called once in the end of each period. + * Note: This method should be called once at the period ending. * */ - function _onPoolsSettled(address[] calldata _poolList) internal { - uint256[] memory _accumulatedRpsList = new uint256[](_poolList.length); + function _recordRewards( + uint256 _period, + address[] calldata _poolAddrs, + uint256[] calldata _rewards + ) internal { + if (_poolAddrs.length != _rewards.length) { + emit PoolsUpdateFailed(_period, _poolAddrs, _rewards); + return; + } + + uint256 _rps; address _poolAddr; - for (uint256 _i; _i < _poolList.length; _i++) { - _poolAddr = _poolList[_i]; - _accumulatedRpsList[_i] = _pendingPool[_poolAddr].accumulatedRps; - - SettledPool storage _sPool = _settledPool[_poolAddr]; - if (_accumulatedRpsList[_i] != _sPool.accumulatedRps) { - _sPool.accumulatedRps = _accumulatedRpsList[_i]; - _sPool.lastSyncedPeriod = _currentPeriod(); + uint256 _stakingTotal; + uint256[] memory _aRps = new uint256[](_poolAddrs.length); + uint256[] memory _shares = new uint256[](_poolAddrs.length); + + for (uint _i = 0; _i < _poolAddrs.length; _i++) { + _poolAddr = _poolAddrs[_i]; + PoolFields storage _pool = _stakingPool[_poolAddr]; + _stakingTotal = stakingTotal(_poolAddr); + + if (_accumulatedRps[_period].lastPeriod == _period) { + _aRps[_i] = _pool.aRps; + _shares[_i] = _pool.shares.inner; + emit PoolUpdateConflicted(_period, _poolAddr); + continue; + } + + // Updates the pool shares if it is outdated + if (_pool.shares.lastPeriod < _period) { + _pool.shares = PeriodWrapper(_stakingTotal, _period); } + + // The rps is 0 if no one stakes for the pool + _rps = _pool.shares.inner == 0 ? 0 : (_rewards[_i] * 1e18) / _pool.shares.inner; + + _aRps[_i] = _pool.aRps += _rps; + _accumulatedRps[_period] = PeriodWrapper(_aRps[_i], _period); + if (_pool.shares.inner != _stakingTotal) { + _pool.shares.inner = _stakingTotal; + } + _shares[_i] = _pool.shares.inner; } - emit SettledPoolsUpdated(_poolList, _accumulatedRpsList); - } - /** - * @dev Returns whether the pool is slashed in the period `_period`. - */ - function _rewardSinked(address _poolAddr, uint256 _period) internal view virtual returns (bool); + emit PoolsUpdated(_period, _poolAddrs, _aRps, _shares); + } /** * @dev Returns the current period. diff --git a/contracts/ronin/staking/Staking.sol b/contracts/ronin/staking/Staking.sol index 6ec6a2b5e..963356031 100644 --- a/contracts/ronin/staking/Staking.sol +++ b/contracts/ronin/staking/Staking.sol @@ -9,9 +9,7 @@ import "./StakingManager.sol"; contract Staking is IStaking, StakingManager, Initializable { /// @dev The minimum threshold for being a validator candidate. - uint256 internal _minValidatorBalance; - /// @dev Mapping from pool address => period index => indicating the pending reward in the period is sinked or not. - mapping(address => mapping(uint256 => bool)) internal _pRewardSinked; + uint256 internal _minValidatorStakingAmount; constructor() { _disableInitializers(); @@ -24,9 +22,9 @@ contract Staking is IStaking, StakingManager, Initializable { /** * @dev Initializes the contract storage. */ - function initialize(address __validatorContract, uint256 __minValidatorBalance) external initializer { + function initialize(address __validatorContract, uint256 __minValidatorStakingAmount) external initializer { _setValidatorContract(__validatorContract); - _setMinValidatorBalance(__minValidatorBalance); + _setMinValidatorStakingAmount(__minValidatorStakingAmount); } /** @@ -38,63 +36,44 @@ contract Staking is IStaking, StakingManager, Initializable { poolExists(_poolAddr) returns ( address _admin, - uint256 _stakedAmount, - uint256 _totalBalance + uint256 _stakingAmount, + uint256 _stakingTotal ) { PoolDetail storage _pool = _stakingPool[_poolAddr]; - return (_pool.admin, _pool.stakedAmount, _pool.totalBalance); + return (_pool.admin, _pool.stakingAmount, _pool.stakingTotal); } /** * @inheritdoc IStaking */ - function minValidatorBalance() public view override(IStaking, StakingManager) returns (uint256) { - return _minValidatorBalance; + function minValidatorStakingAmount() public view override(IStaking, StakingManager) returns (uint256) { + return _minValidatorStakingAmount; } /** * @inheritdoc IStaking */ - function setMinValidatorBalance(uint256 _threshold) external override onlyAdmin { - _setMinValidatorBalance(_threshold); - } - - /////////////////////////////////////////////////////////////////////////////////////// - // FUNCTIONS FOR VALIDATOR // - /////////////////////////////////////////////////////////////////////////////////////// - - /** - * @inheritdoc IStaking - */ - function recordReward(address _consensusAddr, uint256 _reward) external payable onlyValidatorContract { - _recordReward(_consensusAddr, _reward); - } - - /** - * @inheritdoc IStaking - */ - function settleRewardPools(address[] calldata _consensusAddrs) external onlyValidatorContract { - if (_consensusAddrs.length == 0) { - return; - } - _onPoolsSettled(_consensusAddrs); + function setMinValidatorStakingAmount(uint256 _threshold) external override onlyAdmin { + _setMinValidatorStakingAmount(_threshold); } /** * @inheritdoc IStaking */ - function sinkPendingReward(address _consensusAddr) external onlyValidatorContract { - uint256 _period = _currentPeriod(); - _pRewardSinked[_consensusAddr][_period] = true; - _sinkPendingReward(_consensusAddr); + function recordRewards( + uint256 _period, + address[] calldata _consensusAddrs, + uint256[] calldata _rewards + ) external payable onlyValidatorContract { + _recordRewards(_period, _consensusAddrs, _rewards); } /** * @inheritdoc IStaking */ - function deductStakedAmount(address _consensusAddr, uint256 _amount) public onlyValidatorContract { - return _deductStakedAmount(_stakingPool[_consensusAddr], _amount); + function deductStakingAmount(address _consensusAddr, uint256 _amount) external onlyValidatorContract { + return _deductStakingAmount(_stakingPool[_consensusAddr], _amount); } /** @@ -108,40 +87,27 @@ contract Staking is IStaking, StakingManager, Initializable { uint256 _amount; for (uint _i = 0; _i < _pools.length; _i++) { PoolDetail storage _pool = _stakingPool[_pools[_i]]; - _amount = _pool.stakedAmount; - _deductStakedAmount(_pool, _pool.stakedAmount); + _amount = _pool.stakingAmount; if (_amount > 0) { + _deductStakingAmount(_pool, _amount); if (!_sendRON(payable(_pool.admin), _amount)) { - emit StakedAmountDeprecated(_pool.addr, _pool.admin, _amount); + emit StakingAmountDeprecated(_pool.addr, _pool.admin, _amount); } } - - delete _stakingPool[_pool.addr].stakedAmount; } emit PoolsDeprecated(_pools); } - /////////////////////////////////////////////////////////////////////////////////////// - // HELPER FUNCTIONS // - /////////////////////////////////////////////////////////////////////////////////////// - /** * @dev Sets the minimum threshold for being a validator candidate. * - * Emits the `MinValidatorBalanceUpdated` event. + * Emits the `MinValidatorStakingAmountUpdated` event. * */ - function _setMinValidatorBalance(uint256 _threshold) internal { - _minValidatorBalance = _threshold; - emit MinValidatorBalanceUpdated(_threshold); - } - - /** - * @inheritdoc RewardCalculation - */ - function _rewardSinked(address _poolAddr, uint256 _period) internal view virtual override returns (bool) { - return _pRewardSinked[_poolAddr][_period]; + function _setMinValidatorStakingAmount(uint256 _threshold) internal { + _minValidatorStakingAmount = _threshold; + emit MinValidatorStakingAmountUpdated(_threshold); } /** @@ -152,16 +118,16 @@ contract Staking is IStaking, StakingManager, Initializable { } /** - * @dev Deducts from staked amount of the validator `_consensusAddr` for `_amount`. + * @dev Deducts from staking amount of the validator `_consensusAddr` for `_amount`. * * Emits the event `Unstaked`. * */ - function _deductStakedAmount(PoolDetail storage _pool, uint256 _amount) internal { - _amount = Math.min(_pool.stakedAmount, _amount); + function _deductStakingAmount(PoolDetail storage _pool, uint256 _amount) internal { + _amount = Math.min(_pool.stakingAmount, _amount); - _pool.stakedAmount -= _amount; - _changeDelegatedAmount(_pool, _pool.admin, _pool.stakedAmount, _pool.totalBalance - _amount); + _pool.stakingAmount -= _amount; + _changeDelegatingAmount(_pool, _pool.admin, _pool.stakingAmount, _pool.stakingTotal - _amount); emit Unstaked(_pool.addr, _amount); } } diff --git a/contracts/ronin/staking/StakingManager.sol b/contracts/ronin/staking/StakingManager.sol index b9de8b4c0..e5c50f1ab 100644 --- a/contracts/ronin/staking/StakingManager.sol +++ b/contracts/ronin/staking/StakingManager.sol @@ -42,52 +42,52 @@ abstract contract StakingManager is /** * @inheritdoc IRewardPool */ - function balanceOf(address _poolAddr, address _user) + function stakingAmountOf(address _poolAddr, address _user) public view override(IRewardPool, RewardCalculation) returns (uint256) { - return _stakingPool[_poolAddr].delegatedAmount[_user]; + return _stakingPool[_poolAddr].delegatingAmount[_user]; } /** * @inheritdoc IRewardPool */ - function bulkBalanceOf(address[] calldata _poolAddrs, address[] calldata _userList) + function bulkStakingAmountOf(address[] calldata _poolAddrs, address[] calldata _userList) external view override - returns (uint256[] memory _balances) + returns (uint256[] memory _stakingAmounts) { require(_poolAddrs.length > 0 && _poolAddrs.length == _userList.length, "StakingManager: invalid input array"); - _balances = new uint256[](_poolAddrs.length); - for (uint _i = 0; _i < _balances.length; _i++) { - _balances[_i] = _stakingPool[_poolAddrs[_i]].delegatedAmount[_userList[_i]]; + _stakingAmounts = new uint256[](_poolAddrs.length); + for (uint _i = 0; _i < _stakingAmounts.length; _i++) { + _stakingAmounts[_i] = _stakingPool[_poolAddrs[_i]].delegatingAmount[_userList[_i]]; } } /** * @inheritdoc IRewardPool */ - function totalBalance(address _poolAddr) public view override(IRewardPool, RewardCalculation) returns (uint256) { - return _stakingPool[_poolAddr].totalBalance; + function stakingTotal(address _poolAddr) public view override(IRewardPool, RewardCalculation) returns (uint256) { + return _stakingPool[_poolAddr].stakingTotal; } /** * @inheritdoc IRewardPool */ - function totalBalances(address[] calldata _poolList) public view override returns (uint256[] memory _balances) { - _balances = new uint256[](_poolList.length); + function bulkStakingTotal(address[] calldata _poolList) public view override returns (uint256[] memory _stakingAmounts) { + _stakingAmounts = new uint256[](_poolList.length); for (uint _i = 0; _i < _poolList.length; _i++) { - _balances[_i] = totalBalance(_poolList[_i]); + _stakingAmounts[_i] = stakingTotal(_poolList[_i]); } } /** * @inheritdoc IStaking */ - function minValidatorBalance() public view virtual returns (uint256); + function minValidatorStakingAmount() public view virtual returns (uint256); /////////////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR VALIDATOR CANDIDATE // @@ -136,8 +136,8 @@ abstract contract StakingManager is require(_amount > 0, "StakingManager: invalid amount"); address _delegator = msg.sender; PoolDetail storage _pool = _stakingPool[_consensusAddr]; - uint256 _remainAmount = _pool.stakedAmount - _amount; - require(_remainAmount >= minValidatorBalance(), "StakingManager: invalid staked amount left"); + uint256 _remainAmount = _pool.stakingAmount - _amount; + require(_remainAmount >= minValidatorStakingAmount(), "StakingManager: invalid staking amount left"); _unstake(_pool, _delegator, _amount); require(_sendRON(payable(_delegator), _amount), "StakingManager: could not transfer RON"); @@ -160,7 +160,7 @@ abstract contract StakingManager is * Requirements: * - The pool admin is able to receive RON. * - The treasury is able to receive RON. - * - The amount is larger than or equal to the minimum validator balance `minValidatorBalance()`. + * - The amount is larger than or equal to the minimum validator staking amount `minValidatorStakingAmount()`. * * @param _candidateAdmin the candidate admin will be stored in the validator contract, used for calling function that affects * to its candidate. IE: scheduling maintenance. @@ -177,7 +177,7 @@ abstract contract StakingManager is ) internal { require(_sendRON(_poolAdmin, 0), "StakingManager: pool admin cannot receive RON"); require(_sendRON(_treasuryAddr, 0), "StakingManager: treasury cannot receive RON"); - require(_amount >= minValidatorBalance(), "StakingManager: insufficient amount"); + require(_amount >= minValidatorStakingAmount(), "StakingManager: insufficient amount"); _validatorContract.grantValidatorCandidate( _candidateAdmin, @@ -202,13 +202,13 @@ abstract contract StakingManager is address _requester, uint256 _amount ) internal onlyPoolAdmin(_pool, _requester) { - _pool.stakedAmount += _amount; - _changeDelegatedAmount(_pool, _requester, _pool.stakedAmount, _pool.totalBalance + _amount); + _pool.stakingAmount += _amount; + _changeDelegatingAmount(_pool, _requester, _pool.stakingAmount, _pool.stakingTotal + _amount); emit Staked(_pool.addr, _amount); } /** - * @dev Withdraws the staked amount `_amount` for the validator candidate. + * @dev Withdraws the staking amount `_amount` for the validator candidate. * * Requirements: * - The address `_requester` must be the pool admin. @@ -221,10 +221,10 @@ abstract contract StakingManager is address _requester, uint256 _amount ) internal onlyPoolAdmin(_pool, _requester) { - require(_amount <= _pool.stakedAmount, "StakingManager: insufficient staked amount"); + require(_amount <= _pool.stakingAmount, "StakingManager: insufficient staking amount"); - _pool.stakedAmount -= _amount; - _changeDelegatedAmount(_pool, _requester, _pool.stakedAmount, _pool.totalBalance - _amount); + _pool.stakingAmount -= _amount; + _changeDelegatingAmount(_pool, _requester, _pool.stakingAmount, _pool.stakingTotal - _amount); emit Unstaked(_pool.addr, _amount); } @@ -287,19 +287,15 @@ abstract contract StakingManager is function getRewards(address _user, address[] calldata _poolAddrList) external view - returns (uint256[] memory _pendings, uint256[] memory _claimables) + returns (uint256[] memory _rewards) { address _consensusAddr; - _pendings = new uint256[](_poolAddrList.length); - _claimables = new uint256[](_poolAddrList.length); + uint256 _period = _validatorContract.currentPeriod(); + _rewards = new uint256[](_poolAddrList.length); for (uint256 _i = 0; _i < _poolAddrList.length; _i++) { _consensusAddr = _poolAddrList[_i]; - - uint256 _totalReward = getTotalReward(_consensusAddr, _user); - uint256 _claimableReward = getClaimableReward(_consensusAddr, _user); - _pendings[_i] = _totalReward - _claimableReward; - _claimables[_i] = _claimableReward; + _rewards[_i] = _getReward(_consensusAddr, _user, _period, stakingAmountOf(_consensusAddr, _user)); } } @@ -308,7 +304,7 @@ abstract contract StakingManager is */ function claimRewards(address[] calldata _consensusAddrList) external nonReentrant returns (uint256 _amount) { _amount = _claimRewards(msg.sender, _consensusAddrList); - require(_sendRON(payable(msg.sender), _amount), "StakingManager: could not transfer RON"); + _transferRON(payable(msg.sender), _amount); } /** @@ -340,11 +336,11 @@ abstract contract StakingManager is address _delegator, uint256 _amount ) internal notPoolAdmin(_pool, _delegator) { - _changeDelegatedAmount( + _changeDelegatingAmount( _pool, _delegator, - _pool.delegatedAmount[_delegator] + _amount, - _pool.totalBalance + _amount + _pool.delegatingAmount[_delegator] + _amount, + _pool.stakingTotal + _amount ); emit Delegated(_delegator, _pool.addr, _amount); } @@ -355,7 +351,7 @@ abstract contract StakingManager is * Requirements: * - The delegator is not the pool admin. * - The amount is larger than 0. - * - The delegated amount is larger than or equal to the undelegated amount. + * - The delegating amount is larger than or equal to the undelegating amount. * * Emits the `Undelegated` event. * @@ -368,12 +364,12 @@ abstract contract StakingManager is uint256 _amount ) private notPoolAdmin(_pool, _delegator) { require(_amount > 0, "StakingManager: invalid amount"); - require(_pool.delegatedAmount[_delegator] >= _amount, "StakingManager: insufficient amount to undelegate"); - _changeDelegatedAmount( + require(_pool.delegatingAmount[_delegator] >= _amount, "StakingManager: insufficient amount to undelegate"); + _changeDelegatingAmount( _pool, _delegator, - _pool.delegatedAmount[_delegator] - _amount, - _pool.totalBalance - _amount + _pool.delegatingAmount[_delegator] - _amount, + _pool.stakingTotal - _amount ); emit Undelegated(_delegator, _pool.addr, _amount); } @@ -381,15 +377,15 @@ abstract contract StakingManager is /** * @dev Changes the delelgate amount. */ - function _changeDelegatedAmount( + function _changeDelegatingAmount( PoolDetail storage _pool, address _delegator, - uint256 _newDelegateBalance, - uint256 _newTotalBalance + uint256 _newDelegatingAmount, + uint256 _newStakingTotal ) internal { - _syncUserReward(_pool.addr, _delegator, _newDelegateBalance); - _pool.totalBalance = _newTotalBalance; - _pool.delegatedAmount[_delegator] = _newDelegateBalance; + _syncUserReward(_pool.addr, _delegator, _newDelegatingAmount); + _pool.stakingTotal = _newStakingTotal; + _pool.delegatingAmount[_delegator] = _newDelegatingAmount; } /** diff --git a/contracts/ronin/validator/CandidateManager.sol b/contracts/ronin/validator/CandidateManager.sol index 5214983ef..9caedff08 100644 --- a/contracts/ronin/validator/CandidateManager.sol +++ b/contracts/ronin/validator/CandidateManager.sol @@ -3,10 +3,11 @@ pragma solidity ^0.8.9; import "../../extensions/collections/HasStakingContract.sol"; +import "../../extensions/consumers/PercentageConsumer.sol"; import "../../interfaces/ICandidateManager.sol"; import "../../interfaces/IStaking.sol"; -abstract contract CandidateManager is ICandidateManager, HasStakingContract { +abstract contract CandidateManager is ICandidateManager, HasStakingContract, PercentageConsumer { /// @dev Maximum number of validator candidate uint256 private _maxValidatorCandidate; @@ -50,6 +51,7 @@ abstract contract CandidateManager is ICandidateManager, HasStakingContract { uint256 _length = _candidates.length; require(_length < maxValidatorCandidate(), "CandidateManager: exceeds maximum number of candidates"); require(!isValidatorCandidate(_consensusAddr), "CandidateManager: query for already existent candidate"); + require(_commissionRate <= _MAX_PERCENTAGE, "CandidateManager: invalid comission rate"); _candidateIndex[_consensusAddr] = ~_length; _candidates.push(_consensusAddr); @@ -119,29 +121,38 @@ abstract contract CandidateManager is ICandidateManager, HasStakingContract { function numberOfBlocksInEpoch() public view virtual returns (uint256); /** - * @dev Removes unsastisfied candidates, the ones who have insufficient minimum candidate balance, + * @dev Removes unsastisfied candidates, the ones who have insufficient minimum candidate staking amount, * or the ones who revoked their candidate role. * * Emits the event `CandidatesRevoked` when a candidate is revoked. * - * @return _balances a list of balances of the new candidates. + * @return _stakingTotals a list of staking totals of the new candidates. * */ - function _filterUnsatisfiedCandidates(uint256 _minBalance) internal returns (uint256[] memory _balances) { + function _filterUnsatisfiedCandidates() internal returns (uint256[] memory _stakingTotals) { IStaking _staking = _stakingContract; - _balances = _staking.totalBalances(_candidates); + // NOTE: we should filter the ones who does not keep the minium candidate staking amount here + // IE: _staking.bulkSelftStakingAmount(_candidates); + _stakingTotals = _staking.bulkStakingTotal(_candidates); + uint256 _minStakingAmount = _stakingContract.minValidatorStakingAmount(); uint256 _length = _candidates.length; uint256 _period = currentPeriod(); address[] memory _unsatisfiedCandidates = new address[](_length); uint256 _unsatisfiedCount; address _addr; - for (uint _i = 0; _i < _length; _i++) { - _addr = _candidates[_i]; - if (_balances[_i] < _minBalance || _candidateInfo[_addr].revokedPeriod <= _period) { - _balances[_i] = _balances[--_length]; - _unsatisfiedCandidates[_unsatisfiedCount++] = _addr; - _removeCandidate(_addr); + + { + uint256 _i; + while (_i < _length) { + _addr = _candidates[_i]; + if (_stakingTotals[_i] < _minStakingAmount || _candidateInfo[_addr].revokedPeriod <= _period) { + _stakingTotals[_i] = _stakingTotals[--_length]; + _unsatisfiedCandidates[_unsatisfiedCount++] = _addr; + _removeCandidate(_addr); + continue; + } + _i++; } } @@ -154,7 +165,7 @@ abstract contract CandidateManager is ICandidateManager, HasStakingContract { } assembly { - mstore(_balances, _length) + mstore(_stakingTotals, _length) } } diff --git a/contracts/ronin/validator/RoninValidatorSet.sol b/contracts/ronin/validator/RoninValidatorSet.sol index 1bda14b7f..19be96835 100644 --- a/contracts/ronin/validator/RoninValidatorSet.sol +++ b/contracts/ronin/validator/RoninValidatorSet.sol @@ -10,7 +10,6 @@ import "../../extensions/collections/HasSlashIndicatorContract.sol"; import "../../extensions/collections/HasMaintenanceContract.sol"; import "../../extensions/collections/HasRoninTrustedOrganizationContract.sol"; import "../../extensions/collections/HasBridgeTrackingContract.sol"; -import "../../extensions/consumers/PercentageConsumer.sol"; import "../../interfaces/IRoninValidatorSet.sol"; import "../../libraries/Math.sol"; import "../../libraries/EnumFlags.sol"; @@ -30,7 +29,6 @@ contract RoninValidatorSet is HasRoninTrustedOrganizationContract, HasBridgeTrackingContract, CandidateManager, - PercentageConsumer, Initializable { using EnumFlags for EnumFlags.ValidatorFlag; @@ -153,20 +151,20 @@ contract RoninValidatorSet is _totalBridgeReward += _bridgeOperatorBonus; // Deprecates reward for non-validator or slashed validator - if (_requestForBlockProducer) { - uint256 _reward = _submittedReward + _blockProducerBonus; - uint256 _rate = _candidateInfo[_coinbaseAddr].commissionRate; - - uint256 _miningAmount = (_rate * _reward) / 100_00; - _miningReward[_coinbaseAddr] += _miningAmount; - - uint256 _delegatingAmount = _reward - _miningAmount; - _delegatingReward[_coinbaseAddr] += _delegatingAmount; - _stakingContract.recordReward(_coinbaseAddr, _delegatingAmount); - emit BlockRewardSubmitted(_coinbaseAddr, _submittedReward, _blockProducerBonus); - } else { + if (!_requestForBlockProducer) { emit BlockRewardRewardDeprecated(_coinbaseAddr, _submittedReward); + return; } + + uint256 _reward = _submittedReward + _blockProducerBonus; + uint256 _rate = _candidateInfo[_coinbaseAddr].commissionRate; + + uint256 _miningAmount = (_rate * _reward) / _MAX_PERCENTAGE; + _miningReward[_coinbaseAddr] += _miningAmount; + + uint256 _delegatingAmount = _reward - _miningAmount; + _delegatingReward[_coinbaseAddr] += _delegatingAmount; + emit BlockRewardSubmitted(_coinbaseAddr, _submittedReward, _blockProducerBonus); } /** @@ -182,11 +180,11 @@ contract RoninValidatorSet is uint256 _lastPeriod = currentPeriod(); if (_periodEnding) { - uint256 _totalDelegatingReward = _distributeRewardToTreasuriesAndCalculateTotalDelegatingReward( - _lastPeriod, - _currentValidators - ); - _settleAndTransferDelegatingRewards(_currentValidators, _totalDelegatingReward); + ( + uint256 _totalDelegatingReward, + uint256[] memory _delegatingRewards + ) = _distributeRewardToTreasuriesAndCalculateTotalDelegatingReward(_lastPeriod, _currentValidators); + _settleAndTransferDelegatingRewards(_lastPeriod, _currentValidators, _totalDelegatingReward, _delegatingRewards); _slashIndicatorContract.updateCreditScore(_currentValidators, _lastPeriod); _currentValidators = _syncValidatorSet(_newPeriod); } @@ -212,14 +210,13 @@ contract RoninValidatorSet is _miningRewardDeprecatedAtPeriod[_validatorAddr][_period] = true; delete _miningReward[_validatorAddr]; delete _delegatingReward[_validatorAddr]; - IStaking(_stakingContract).sinkPendingReward(_validatorAddr); if (_newJailedUntil > 0) { _jailedUntil[_validatorAddr] = Math.max(_newJailedUntil, _jailedUntil[_validatorAddr]); } if (_slashAmount > 0) { - IStaking(_stakingContract).deductStakedAmount(_validatorAddr, _slashAmount); + IStaking(_stakingContract).deductStakingAmount(_validatorAddr, _slashAmount); } emit ValidatorPunished(_validatorAddr, _period, _jailedUntil[_validatorAddr], _slashAmount, true, false); @@ -516,7 +513,8 @@ contract RoninValidatorSet is function _distributeRewardToTreasuriesAndCalculateTotalDelegatingReward( uint256 _lastPeriod, address[] memory _currentValidators - ) private returns (uint256 _totalDelegatingReward) { + ) private returns (uint256 _totalDelegatingReward, uint256[] memory _delegatingRewards) { + _delegatingRewards = new uint256[](_currentValidators.length); address _consensusAddr; address payable _treasury; IBridgeTracking _bridgeTracking = _bridgeTrackingContract; @@ -550,6 +548,7 @@ contract RoninValidatorSet is if (!_jailed(_consensusAddr) && !_miningRewardDeprecated(_consensusAddr, _lastPeriod)) { _totalDelegatingReward += _delegatingReward[_consensusAddr]; + _delegatingRewards[_i] = _delegatingReward[_consensusAddr]; _distributeMiningReward(_consensusAddr, _treasury); } @@ -658,14 +657,16 @@ contract RoninValidatorSet is * Note: This method should be called once in the end of each period. * */ - function _settleAndTransferDelegatingRewards(address[] memory _currentValidators, uint256 _totalDelegatingReward) - private - { + function _settleAndTransferDelegatingRewards( + uint256 _period, + address[] memory _currentValidators, + uint256 _totalDelegatingReward, + uint256[] memory _delegatingRewards + ) private { IStaking _staking = _stakingContract; - _staking.settleRewardPools(_currentValidators); - if (_totalDelegatingReward > 0) { if (_sendRON(payable(address(_staking)), _totalDelegatingReward)) { + _staking.recordRewards(_period, _currentValidators, _delegatingRewards); emit StakingRewardDistributed(_totalDelegatingReward); return; } @@ -684,16 +685,14 @@ contract RoninValidatorSet is * */ function _syncValidatorSet(uint256 _newPeriod) private returns (address[] memory _newValidators) { - uint256[] memory _balanceWeights; - // This is a temporary approach since the slashing issue is still not finalized. + // NOTE: This is a temporary approach since the slashing issue is still not finalized. // Read more about slashing issue at: https://www.notion.so/skymavis/Slashing-Issue-9610ae1452434faca1213ab2e1d7d944 - uint256 _minBalance = _stakingContract.minValidatorBalance(); - _balanceWeights = _filterUnsatisfiedCandidates(_minBalance); + uint256[] memory _weights = _filterUnsatisfiedCandidates(); uint256[] memory _trustedWeights = _roninTrustedOrganizationContract.getConsensusWeights(_candidates); uint256 _newValidatorCount; (_newValidators, _newValidatorCount) = _pcPickValidatorSet( _candidates, - _balanceWeights, + _weights, _trustedWeights, _maxValidatorNumber, _maxPrioritizedValidatorNumber diff --git a/src/config.ts b/src/config.ts index 2127201af..8d3a6f74a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -63,7 +63,7 @@ export const maintenanceConf: MaintenanceConfig = { export const stakingConfig: StakingConfig = { [Network.Hardhat]: undefined, [Network.Devnet]: { - minValidatorBalance: BigNumber.from(10).pow(18).mul(BigNumber.from(10).pow(5)), // 100.000 RON + minValidatorStakingAmount: BigNumber.from(10).pow(18).mul(BigNumber.from(10).pow(5)), // 100.000 RON }, [Network.Testnet]: undefined, [Network.Mainnet]: undefined, @@ -147,7 +147,6 @@ export const mainchainGovernanceAdminConf: MainchainGovernanceAdminConfig = { [Network.Hardhat]: undefined, [Network.Devnet]: { roleSetter: '0x93b8eed0a1e082ae2f478fd7f8c14b1fc0261bb1', - // bridgeContract: ethers.constants.AddressZero, relayers: ['0x93b8eed0a1e082ae2f478fd7f8c14b1fc0261bb1'], }, [Network.Goerli]: undefined, diff --git a/src/deploy/proxy/staking-proxy.ts b/src/deploy/proxy/staking-proxy.ts index 38cb14b2d..82d030148 100644 --- a/src/deploy/proxy/staking-proxy.ts +++ b/src/deploy/proxy/staking-proxy.ts @@ -17,7 +17,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme const data = new Staking__factory().interface.encodeFunctionData('initialize', [ generalRoninConf[network.name]!.validatorContract?.address, - stakingConfig[network.name]!.minValidatorBalance, + stakingConfig[network.name]!.minValidatorStakingAmount, ]); const deployment = await deploy('StakingProxy', { diff --git a/src/utils.ts b/src/utils.ts index 1558ff0e0..75381d9dd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -71,7 +71,7 @@ export interface MaintenanceConfig { } export interface StakingArguments { - minValidatorBalance?: BigNumberish; + minValidatorStakingAmount?: BigNumberish; } export interface StakingConfig { diff --git a/test/governance-admin/GovernanceAdmin.test.ts b/test/governance-admin/GovernanceAdmin.test.ts index 456b6c0f5..06cc4ee71 100644 --- a/test/governance-admin/GovernanceAdmin.test.ts +++ b/test/governance-admin/GovernanceAdmin.test.ts @@ -74,7 +74,7 @@ describe('Governance Admin test', () => { stakingContract.address, 0, governanceAdminInterface.interface.encodeFunctionData('functionDelegateCall', [ - stakingContract.interface.encodeFunctionData('setMinValidatorBalance', [555]), + stakingContract.interface.encodeFunctionData('setMinValidatorStakingAmount', [555]), ]), 500_000 ); @@ -82,7 +82,7 @@ describe('Governance Admin test', () => { supports = signatures.map(() => VoteType.For); await governanceAdmin.connect(governors[0]).proposeProposalStructAndCastVotes(proposal, supports, signatures); - expect(await stakingContract.minValidatorBalance()).eq(555); + expect(await stakingContract.minValidatorStakingAmount()).eq(555); }); it('Should not be able to reuse already voted signatures or proposals', async () => { diff --git a/test/helpers/fixture.ts b/test/helpers/fixture.ts index 37f85b264..b2a0532db 100644 --- a/test/helpers/fixture.ts +++ b/test/helpers/fixture.ts @@ -62,7 +62,7 @@ export const defaultTestConfig: InitTestInput = { }, stakingArguments: { - minValidatorBalance: BigNumber.from(100), + minValidatorStakingAmount: BigNumber.from(100), }, stakingVestingArguments: { diff --git a/test/helpers/reward-calculation.ts b/test/helpers/reward-calculation.ts deleted file mode 100644 index 107fe3825..000000000 --- a/test/helpers/reward-calculation.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { expect } from 'chai'; -import { BigNumberish, ContractTransaction } from 'ethers'; - -import { expectEvent } from './utils'; -import { RewardCalculation__factory } from '../../src/types'; - -const contractInterface = RewardCalculation__factory.createInterface(); - -export const expects = { - emitSettledPoolsUpdatedEvent: async function ( - tx: ContractTransaction, - expectingPoolList?: string[], - expectingAccumulatedRpsList?: BigNumberish[] - ) { - await expectEvent( - contractInterface, - 'SettledPoolsUpdated', - tx, - (event) => { - if (expectingPoolList) { - expect(event.args[0], 'invalid pool list').eql(expectingPoolList); - } - if (expectingAccumulatedRpsList) { - expect(event.args[1], 'invalid accumulated rps list').eql(expectingAccumulatedRpsList); - } - }, - 1 - ); - }, -}; diff --git a/test/helpers/staking.ts b/test/helpers/staking.ts index 112814bf5..6383bd541 100644 --- a/test/helpers/staking.ts +++ b/test/helpers/staking.ts @@ -7,18 +7,26 @@ import { Staking__factory } from '../../src/types'; const contractInterface = Staking__factory.createInterface(); export const expects = { - emitSettledPoolsUpdatedEvent: async function ( + emitPoolsUpdatedEvent: async function ( tx: ContractTransaction, - expectingPoolAddressList: string[], - expectingAccumulatedRpsList: BigNumberish[] + expectingPeriod?: BigNumberish, + expectingPoolAddressList?: string[], + expectingAccumulatedRpsList?: BigNumberish[] ) { await expectEvent( contractInterface, - 'SettledPoolsUpdated', + 'PoolsUpdated', tx, (event) => { - expect(event.args[0], 'invalid pool address list').eql(expectingPoolAddressList); - expect(event.args[1], 'invalid accumulated rps list').eql(expectingAccumulatedRpsList); + if (!!expectingPeriod) { + expect(event.args[0], 'invalid period').eql(expectingPeriod); + } + if (!!expectingPoolAddressList) { + expect(event.args[1], 'invalid pool address list').eql(expectingPoolAddressList); + } + if (!!expectingAccumulatedRpsList) { + expect(event.args[2], 'invalid accumulated rps list').eql(expectingAccumulatedRpsList); + } }, 1 ); diff --git a/test/integration/ActionSlashValidators.test.ts b/test/integration/ActionSlashValidators.test.ts index ae490cd82..be70a6d06 100644 --- a/test/integration/ActionSlashValidators.test.ts +++ b/test/integration/ActionSlashValidators.test.ts @@ -38,7 +38,7 @@ const unavailabilityTier1Threshold = 10; const unavailabilityTier2Threshold = 20; const slashAmountForUnavailabilityTier2Threshold = BigNumber.from(1); const slashDoubleSignAmount = 1000; -const minValidatorBalance = BigNumber.from(100); +const minValidatorStakingAmount = BigNumber.from(100); describe('[Integration] Slash validators', () => { before(async () => { @@ -59,7 +59,7 @@ describe('[Integration] Slash validators', () => { }, }, stakingArguments: { - minValidatorBalance, + minValidatorStakingAmount, }, roninTrustedOrganizationArguments: { trustedOrganizations: [ @@ -131,14 +131,14 @@ describe('[Integration] Slash validators', () => { before(async () => { slasheeIdx = 2; slashee = validatorCandidates[slasheeIdx]; - slasheeInitStakingAmount = minValidatorBalance.add(slashAmountForUnavailabilityTier2Threshold.mul(10)); + slasheeInitStakingAmount = minValidatorStakingAmount.add(slashAmountForUnavailabilityTier2Threshold.mul(10)); await stakingContract .connect(slashee) .applyValidatorCandidate(slashee.address, slashee.address, slashee.address, slashee.address, 2_00, { value: slasheeInitStakingAmount, }); - expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(slasheeInitStakingAmount); + expect(await stakingContract.stakingAmountOf(slashee.address, slashee.address)).eq(slasheeInitStakingAmount); await EpochController.setTimestampToPeriodEnding(); await mineBatchTxs(async () => { @@ -192,9 +192,11 @@ describe('[Integration] Slash validators', () => { .withArgs(slashee.address, slashAmountForUnavailabilityTier2Threshold); }); - it('Should the Staking contract subtract staked amount from validator', async () => { + it('Should the Staking contract subtract staking amount from validator', async () => { let _expectingSlasheeStakingAmount = slasheeInitStakingAmount.sub(slashAmountForUnavailabilityTier2Threshold); - expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(_expectingSlasheeStakingAmount); + expect(await stakingContract.stakingAmountOf(slashee.address, slashee.address)).eq( + _expectingSlasheeStakingAmount + ); }); it('Should the block producer set exclude the jailed validator in the next epoch', async () => { @@ -258,7 +260,7 @@ describe('[Integration] Slash validators', () => { before(async () => { slasheeIdx = 3; slashee = validatorCandidates[slasheeIdx]; - slasheeInitStakingAmount = minValidatorBalance; + slasheeInitStakingAmount = minValidatorStakingAmount; await stakingContract .connect(slashee) @@ -266,7 +268,7 @@ describe('[Integration] Slash validators', () => { value: slasheeInitStakingAmount, }); - expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(slasheeInitStakingAmount); + expect(await stakingContract.stakingAmountOf(slashee.address, slashee.address)).eq(slasheeInitStakingAmount); await EpochController.setTimestampToPeriodEnding(); await mineBatchTxs(async () => { @@ -281,7 +283,7 @@ describe('[Integration] Slash validators', () => { expect(await validatorContract.getValidators()).eql(expectingValidatorSet); }); - describe('Check effects on indicator and staked amount', async () => { + describe('Check effects on indicator and staking amount', async () => { it('Should the ValidatorSet contract emit event', async () => { for (let i = 0; i < unavailabilityTier2Threshold - 1; i++) { await slashContract.connect(coinbase).slashUnavailability(slashee.address); @@ -319,9 +321,11 @@ describe('[Integration] Slash validators', () => { .withArgs(slashee.address, slashAmountForUnavailabilityTier2Threshold); }); - it('Should the Staking contract subtract staked amount from validator', async () => { + it('Should the Staking contract subtract staking amount from validator', async () => { let _expectingSlasheeStakingAmount = slasheeInitStakingAmount.sub(slashAmountForUnavailabilityTier2Threshold); - expect(await stakingContract.balanceOf(slashee.address, slashee.address)).eq(_expectingSlasheeStakingAmount); + expect(await stakingContract.stakingAmountOf(slashee.address, slashee.address)).eq( + _expectingSlasheeStakingAmount + ); }); }); diff --git a/test/integration/ActionSubmitReward.test.ts b/test/integration/ActionSubmitReward.test.ts index f47c853a4..4fa68f3a3 100644 --- a/test/integration/ActionSubmitReward.test.ts +++ b/test/integration/ActionSubmitReward.test.ts @@ -1,7 +1,6 @@ import { expect } from 'chai'; import { network, ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs'; import { BigNumber, ContractTransaction } from 'ethers'; import { @@ -33,7 +32,7 @@ let validatorCandidates: SignerWithAddress[]; const unavailabilityTier2Threshold = 10; const slashAmountForUnavailabilityTier2Threshold = BigNumber.from(1); const slashDoubleSignAmount = 1000; -const minValidatorBalance = BigNumber.from(100); +const minValidatorStakingAmount = BigNumber.from(100); const blockProducerBonusPerBlock = BigNumber.from(1); describe('[Integration] Submit Block Reward', () => { @@ -55,7 +54,7 @@ describe('[Integration] Submit Block Reward', () => { }, }, stakingArguments: { - minValidatorBalance, + minValidatorStakingAmount, }, stakingVestingArguments: { blockProducerBonusPerBlock, @@ -108,7 +107,7 @@ describe('[Integration] Submit Block Reward', () => { let submitRewardTx: ContractTransaction; before(async () => { - let initStakingAmount = minValidatorBalance.mul(2); + let initStakingAmount = minValidatorStakingAmount.mul(2); validator = validatorCandidates[0]; await stakingContract .connect(validator) @@ -144,10 +143,6 @@ describe('[Integration] Submit Block Reward', () => { it.skip('Should the ValidatorSetContract update mining reward', async () => {}); - it('Should the StakingContract emit event of recording reward', async () => { - await expect(submitRewardTx).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(validator.address, anyValue); - }); - it.skip('Should the StakingContract record update for new block reward', async () => {}); }); @@ -156,7 +151,7 @@ describe('[Integration] Submit Block Reward', () => { let submitRewardTx: ContractTransaction; before(async () => { - let initStakingAmount = minValidatorBalance.mul(2); + let initStakingAmount = minValidatorStakingAmount.mul(2); validator = validatorCandidates[1]; await stakingContract diff --git a/test/integration/ActionWrapUpEpoch.test.ts b/test/integration/ActionWrapUpEpoch.test.ts index 4938f4370..2309d51ed 100644 --- a/test/integration/ActionWrapUpEpoch.test.ts +++ b/test/integration/ActionWrapUpEpoch.test.ts @@ -13,7 +13,7 @@ import { RoninGovernanceAdmin, RoninGovernanceAdmin__factory, } from '../../src/types'; -import { expects as StakingExpects } from '../helpers/reward-calculation'; +import { expects as StakingExpects } from '../helpers/staking'; import { EpochController, expects as ValidatorSetExpects } from '../helpers/ronin-validator-set'; import { mineBatchTxs } from '../helpers/utils'; import { initTest } from '../helpers/fixture'; @@ -34,7 +34,7 @@ let validatorCandidates: SignerWithAddress[]; const unavailabilityTier2Threshold = 10; const slashAmountForUnavailabilityTier2Threshold = BigNumber.from(1); const slashDoubleSignAmount = 1000; -const minValidatorBalance = BigNumber.from(100); +const minValidatorStakingAmount = BigNumber.from(100); const maxValidatorNumber = 3; describe('[Integration] Wrap up epoch', () => { @@ -56,7 +56,7 @@ describe('[Integration] Wrap up epoch', () => { }, }, stakingArguments: { - minValidatorBalance, + minValidatorStakingAmount, }, roninTrustedOrganizationArguments: { trustedOrganizations: [governor].map((v) => ({ @@ -131,7 +131,7 @@ describe('[Integration] Wrap up epoch', () => { validatorCandidates[i].address, 2_00, { - value: minValidatorBalance.mul(2).add(i), + value: minValidatorStakingAmount.mul(2).add(i), } ); } @@ -158,7 +158,6 @@ describe('[Integration] Wrap up epoch', () => { await validatorContract.endEpoch(); await validatorContract.wrapUpEpoch(); let duplicatedWrapUpTx = validatorContract.wrapUpEpoch(); - await expect(duplicatedWrapUpTx).to.be.revertedWith('RoninValidatorSet: query for already wrapped up epoch'); }); }); @@ -175,8 +174,9 @@ describe('[Integration] Wrap up epoch', () => { describe('StakingContract internal actions: settle reward pool', async () => { it('Should the StakingContract emit event of settling reward', async () => { - await StakingExpects.emitSettledPoolsUpdatedEvent( + await StakingExpects.emitPoolsUpdatedEvent( wrapUpTx, + undefined, validators .slice(1, 4) .map((_) => _.address) @@ -232,7 +232,7 @@ describe('[Integration] Wrap up epoch', () => { validators[i].address, 2_00, { - value: minValidatorBalance.mul(3).add(i), + value: minValidatorStakingAmount.mul(3).add(i), } ); } diff --git a/test/integration/Configuration.test.ts b/test/integration/Configuration.test.ts index 87df09058..5646eb472 100644 --- a/test/integration/Configuration.test.ts +++ b/test/integration/Configuration.test.ts @@ -50,7 +50,7 @@ const config: InitTestInput = { }, stakingArguments: { - minValidatorBalance: BigNumber.from(100), + minValidatorStakingAmount: BigNumber.from(100), }, stakingVestingArguments: { blockProducerBonusPerBlock: 1, @@ -191,7 +191,7 @@ describe('[Integration] Configuration check', () => { it('Should the StakingContract contract set configs correctly', async () => { expect(await stakingContract.validatorContract()).to.eq(validatorContract.address); - expect(await stakingContract.minValidatorBalance()).to.eq(config.stakingArguments?.minValidatorBalance); + expect(await stakingContract.minValidatorStakingAmount()).to.eq(config.stakingArguments?.minValidatorStakingAmount); }); it('Should the StakingVestingContract contract set configs correctly', async () => { diff --git a/test/maintainance/Maintenance.test.ts b/test/maintainance/Maintenance.test.ts index 67749c752..202f43752 100644 --- a/test/maintainance/Maintenance.test.ts +++ b/test/maintainance/Maintenance.test.ts @@ -37,7 +37,7 @@ const unavailabilityTier1Threshold = 50; const unavailabilityTier2Threshold = 150; const maxValidatorNumber = 4; const numberOfBlocksInEpoch = 600; -const minValidatorBalance = BigNumber.from(100); +const minValidatorStakingAmount = BigNumber.from(100); const minMaintenanceBlockPeriod = 100; const maxMaintenanceBlockPeriod = 1000; const minOffset = 200; @@ -63,7 +63,7 @@ describe('Maintenance test', () => { }, }, stakingArguments: { - minValidatorBalance, + minValidatorStakingAmount, }, roninValidatorSetArguments: { maxValidatorNumber, @@ -105,7 +105,7 @@ describe('Maintenance test', () => { validatorCandidates[i].address, validatorCandidates[i].address, 1, - { value: minValidatorBalance.add(maxValidatorNumber).sub(i) } + { value: minValidatorStakingAmount.add(maxValidatorNumber).sub(i) } ); } diff --git a/test/slash/CreditScore.test.ts b/test/slash/CreditScore.test.ts index 9e5458916..938fb40d7 100644 --- a/test/slash/CreditScore.test.ts +++ b/test/slash/CreditScore.test.ts @@ -45,7 +45,7 @@ const unavailabilityTier1Threshold = 5; const unavailabilityTier2Threshold = 15; const slashAmountForUnavailabilityTier2Threshold = 2; -const minValidatorBalance = BigNumber.from(100); +const minValidatorStakingAmount = BigNumber.from(100); const maxValidatorCandidate = 3; const maxValidatorNumber = 2; const numberOfBlocksInEpoch = 600; @@ -126,7 +126,7 @@ describe('Credit score and bail out test', () => { }, }, stakingArguments: { - minValidatorBalance, + minValidatorStakingAmount, }, roninValidatorSetArguments: { maxValidatorNumber, @@ -170,7 +170,7 @@ describe('Credit score and bail out test', () => { validatorCandidates[i].address, validatorCandidates[i].address, 1, - { value: minValidatorBalance.mul(2).sub(i) } + { value: minValidatorStakingAmount.mul(2).sub(i) } ); } diff --git a/test/slash/SlashIndicator.test.ts b/test/slash/SlashIndicator.test.ts index e227e7e15..ae220b7af 100644 --- a/test/slash/SlashIndicator.test.ts +++ b/test/slash/SlashIndicator.test.ts @@ -39,7 +39,7 @@ const unavailabilityTier2Threshold = 10; const maxValidatorNumber = 21; const maxValidatorCandidate = 50; const numberOfBlocksInEpoch = 600; -const minValidatorBalance = BigNumber.from(100); +const minValidatorStakingAmount = BigNumber.from(100); const slashAmountForUnavailabilityTier2Threshold = BigNumber.from(2); const slashDoubleSignAmount = BigNumber.from(5); @@ -69,7 +69,7 @@ describe('Slash indicator test', () => { }, }, stakingArguments: { - minValidatorBalance, + minValidatorStakingAmount, }, roninValidatorSetArguments: { maxValidatorNumber, @@ -114,7 +114,7 @@ describe('Slash indicator test', () => { validatorCandidates[i].address, validatorCandidates[i].address, 1, - { value: minValidatorBalance.mul(2).add(maxValidatorNumber).sub(i) } + { value: minValidatorStakingAmount.mul(2).add(maxValidatorNumber).sub(i) } ); } diff --git a/test/staking/AdvancedCalculation.test.ts b/test/staking/AdvancedCalculation.test.ts deleted file mode 100644 index 102317633..000000000 --- a/test/staking/AdvancedCalculation.test.ts +++ /dev/null @@ -1,586 +0,0 @@ -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { expect } from 'chai'; -import { BigNumber, BigNumberish, ContractTransaction } from 'ethers'; -import { ethers, network } from 'hardhat'; - -import { MockStaking, MockStaking__factory } from '../../src/types'; -import * as StakingContract from '../helpers/staking'; - -const EPS = 1; -const MASK = BigNumber.from(10).pow(18); -const poolAddr = ethers.constants.AddressZero; - -let deployer: SignerWithAddress; -let userA: SignerWithAddress; -let userB: SignerWithAddress; -let stakingContract: MockStaking; - -const local = { - balanceA: BigNumber.from(0), - balanceB: BigNumber.from(0), - accumulatedRewardForA: BigNumber.from(0), - accumulatedRewardForB: BigNumber.from(0), - claimableRewardForA: BigNumber.from(0), - claimableRewardForB: BigNumber.from(0), - aRps: BigNumber.from(0), - settledARps: BigNumber.from(0), - syncBalance: async function () { - this.balanceA = await stakingContract.balanceOf(poolAddr, userA.address); - this.balanceB = await stakingContract.balanceOf(poolAddr, userB.address); - }, - recordReward: async function (reward: BigNumberish) { - const totalStaked = await stakingContract.totalBalance(poolAddr); - await this.syncBalance(); - this.accumulatedRewardForA = this.accumulatedRewardForA.add( - BigNumber.from(reward).mul(this.balanceA).div(totalStaked) - ); - this.accumulatedRewardForB = this.accumulatedRewardForB.add( - BigNumber.from(reward).mul(this.balanceB).div(totalStaked) - ); - this.aRps = this.aRps.add(BigNumber.from(reward).mul(MASK).div(totalStaked)); - }, - settledPools: function () { - this.claimableRewardForA = this.accumulatedRewardForA; - this.claimableRewardForB = this.accumulatedRewardForB; - this.settledARps = this.aRps; - }, - slash: function () { - this.accumulatedRewardForA = this.claimableRewardForA; - this.accumulatedRewardForB = this.claimableRewardForB; - this.aRps = this.settledARps; - }, - reset: function () { - this.claimableRewardForA = BigNumber.from(0); - this.claimableRewardForB = BigNumber.from(0); - this.accumulatedRewardForA = BigNumber.from(0); - this.accumulatedRewardForB = BigNumber.from(0); - this.aRps = BigNumber.from(0); - this.settledARps = BigNumber.from(0); - this.balanceA = BigNumber.from(0); - this.balanceB = BigNumber.from(0); - }, - claimRewardForA: function () { - this.accumulatedRewardForA = this.accumulatedRewardForA.sub(this.claimableRewardForA); - this.claimableRewardForA = BigNumber.from(0); - }, - claimRewardForB: function () { - this.accumulatedRewardForB = this.accumulatedRewardForB.sub(this.claimableRewardForB); - this.claimableRewardForB = BigNumber.from(0); - }, -}; - -const expectLocalCalculationRight = async () => { - { - const userReward = await stakingContract.getTotalReward(poolAddr, userA.address); - expect( - userReward.sub(local.accumulatedRewardForA).abs().lte(EPS), - `invalid user reward for A expected=${local.accumulatedRewardForA.toString()} actual=${userReward}` - ).to.be.true; - const claimableReward = await stakingContract.getClaimableReward(poolAddr, userA.address); - expect( - claimableReward.sub(local.claimableRewardForA).abs().lte(EPS), - `invalid claimable reward for A expected=${local.claimableRewardForA.toString()} actual=${claimableReward}` - ).to.be.true; - } - { - const userReward = await stakingContract.getTotalReward(poolAddr, userB.address); - expect( - userReward.sub(local.accumulatedRewardForB).abs().lte(EPS), - `invalid user reward for B expected=${local.accumulatedRewardForB.toString()} actual=${userReward}` - ).to.be.true; - const claimableReward = await stakingContract.getClaimableReward(poolAddr, userB.address); - expect( - claimableReward.sub(local.claimableRewardForB).abs().lte(EPS), - `invalid claimable reward for B expected=${local.claimableRewardForB.toString()} actual=${claimableReward}` - ).to.be.true; - } -}; - -describe('Advanced Calculation test', () => { - let tx: ContractTransaction; - const txs: ContractTransaction[] = []; - - before(async () => { - [deployer, userA, userB] = await ethers.getSigners(); - stakingContract = await new MockStaking__factory(deployer).deploy(poolAddr); - await network.provider.send('evm_setAutomine', [false]); - local.reset(); - }); - - after(async () => { - await network.provider.send('evm_setAutomine', [true]); - }); - - it('Should work properly with staking actions occurring sequentially for a period that will be settled', async () => { - txs[0] = await stakingContract.stake(userA.address, 100); - txs[1] = await stakingContract.stake(userB.address, 100); - await network.provider.send('evm_mine'); - await expect(txs[0]!).emit(stakingContract, 'PendingRewardUpdated').withArgs(poolAddr, userA.address, 0, 0); - await expect(txs[1]!).emit(stakingContract, 'PendingRewardUpdated').withArgs(poolAddr, userB.address, 0, 0); - - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - txs[0] = await stakingContract.stake(userA.address, 200); - await network.provider.send('evm_mine'); - await local.syncBalance(); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 1000, local.aRps.mul(local.balanceA).div(MASK)); - await expectLocalCalculationRight(); - - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - txs[0] = await stakingContract.unstake(userA.address, 200); - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await local.syncBalance(); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 1750, local.aRps.mul(local.balanceA).div(MASK)); - await local.recordReward(1000); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - txs[0] = await stakingContract.stake(userA.address, 200); - await network.provider.send('evm_mine'); - await local.syncBalance(); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK)); - await expectLocalCalculationRight(); - - tx = await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await network.provider.send('evm_mine'); - await StakingContract.expects.emitSettledPoolsUpdatedEvent(tx!, [poolAddr], [local.aRps]); - local.settledPools(); - await expectLocalCalculationRight(); - }); - - it('Should work properly with staking actions occurring sequentially for a period that will be slashed', async () => { - txs[0] = await stakingContract.stake(userA.address, 100); - await network.provider.send('evm_mine'); - await expectLocalCalculationRight(); - await expect(txs[0]!) - .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userA.address, 2250, local.aRps); - await local.syncBalance(); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK)); - - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - - txs[0] = await stakingContract.stake(userA.address, 300); - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await local.syncBalance(); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 3850, local.aRps.mul(local.balanceA).div(MASK)); - await local.recordReward(1000); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - tx = await stakingContract.slash(); - await network.provider.send('evm_mine'); - local.slash(); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - tx = await stakingContract.recordReward(0); - await network.provider.send('evm_mine'); - await local.recordReward(0); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - - txs[0] = await stakingContract.unstake(userA.address, 300); - await network.provider.send('evm_mine'); - await local.syncBalance(); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK)); - txs[0] = await stakingContract.unstake(userA.address, 50); - await network.provider.send('evm_mine'); - await expectLocalCalculationRight(); - await local.syncBalance(); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK)); - - tx = await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await network.provider.send('evm_mine'); - await expectLocalCalculationRight(); - await StakingContract.expects.emitSettledPoolsUpdatedEvent(tx!, [poolAddr], [local.aRps]); - }); - - it('Should work properly with staking actions occurring sequentially for a period that will be slashed again', async () => { - txs[0] = await stakingContract.stake(userA.address, 50); - await network.provider.send('evm_mine'); - await local.syncBalance(); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK)); - await expectLocalCalculationRight(); - - txs[0] = await stakingContract.claimReward(userA.address); - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await expect(txs[0]!) - .emit(stakingContract, 'RewardClaimed') - .withArgs(poolAddr, userA.address, local.claimableRewardForA); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 2250, local.aRps.mul(local.balanceA).div(MASK).add(local.claimableRewardForA)); - await expect(txs[0]!) - .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userA.address, 0, local.settledARps); - local.claimRewardForA(); - await local.recordReward(1000); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - txs[0] = await stakingContract.stake(userA.address, 300); - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await local.syncBalance(); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 1600, local.aRps.mul(local.balanceA).div(MASK)); - await local.recordReward(1000); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - tx = await stakingContract.slash(); - await network.provider.send('evm_mine'); - local.slash(); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - tx = await stakingContract.recordReward(0); - await network.provider.send('evm_mine'); - await local.recordReward(0); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - await network.provider.send('evm_mine'); - - txs[0] = await stakingContract.unstake(userA.address, 300); - await network.provider.send('evm_mine'); - await local.syncBalance(); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 0, local.aRps.mul(local.balanceA).div(MASK)); - - txs[0] = await stakingContract.unstake(userA.address, 100); - await network.provider.send('evm_mine'); - await local.syncBalance(); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 0, local.aRps.mul(local.balanceA).div(MASK)); - await expectLocalCalculationRight(); - - txs[1] = await stakingContract.claimReward(userB.address); - tx = await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await network.provider.send('evm_mine'); - await expect(txs[1]!) - .emit(stakingContract, 'RewardClaimed') - .withArgs(poolAddr, userB.address, local.claimableRewardForB); - await expect(txs[1]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userB.address, 0, local.aRps.mul(local.balanceB).div(MASK)); - await expect(txs[1]!) - .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userB.address, 0, local.settledARps); - local.claimRewardForB(); - await StakingContract.expects.emitSettledPoolsUpdatedEvent(tx!, [poolAddr], [local.aRps]); - local.settledPools(); - await expectLocalCalculationRight(); - }); - - it('Should be able to calculate right reward after claiming', async () => { - const lastCredited = local.aRps.mul(300).div(MASK); - - txs[0] = await stakingContract.recordReward(1000); - txs[1] = await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expect(txs[0]!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await StakingContract.expects.emitSettledPoolsUpdatedEvent(txs[1]!, [poolAddr], [local.aRps]); - local.settledPools(); - await expectLocalCalculationRight(); - - txs[0] = await stakingContract.claimReward(userA.address); - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await expect(txs[0]!) - .emit(stakingContract, 'RewardClaimed') - .withArgs(poolAddr, userA.address, local.claimableRewardForA); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 0, lastCredited.add(local.claimableRewardForA)); - await expect(txs[0]!) - .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userA.address, 0, local.aRps); - local.claimRewardForA(); - await local.recordReward(1000); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - txs[0] = await stakingContract.claimReward(userA.address); - await network.provider.send('evm_mine'); - local.claimRewardForA(); - await expectLocalCalculationRight(); - await expect(txs[0]!).emit(stakingContract, 'RewardClaimed').withArgs(poolAddr, userA.address, 0); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 0, lastCredited.add(750)); - await expect(txs[0]!) - .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userA.address, 0, local.settledARps); - - txs[1] = await stakingContract.claimReward(userB.address); - tx = await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await network.provider.send('evm_mine'); - await expect(txs[1]!).emit(stakingContract, 'RewardClaimed').withArgs(poolAddr, userB.address, 250); - await expect(txs[1]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userB.address, 0, 1750 + 250); - await expect(txs[1]!) - .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userB.address, 0, local.settledARps); - local.claimRewardForB(); - local.settledPools(); - await StakingContract.expects.emitSettledPoolsUpdatedEvent(tx!, [poolAddr], [local.settledARps]); - await expectLocalCalculationRight(); - }); - - it('Should work properly with staking actions from multi-users occurring in the same block', async () => { - txs[0] = await stakingContract.stake(userA.address, 100); - await network.provider.send('evm_mine'); - await expect(txs[0]!) - .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userA.address, local.claimableRewardForA, local.settledARps); - await local.syncBalance(); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, local.claimableRewardForA, local.aRps.mul(local.balanceA).div(MASK)); - - txs[0] = await stakingContract.stake(userA.address, 300); - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await local.syncBalance(); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 750, local.aRps.mul(local.balanceA).div(MASK)); - await local.recordReward(1000); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - txs[1] = await stakingContract.stake(userB.address, 200); - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await expect(txs[1]!) - .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userB.address, local.claimableRewardForB, local.settledARps); - await local.recordReward(1000); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - txs[1] = await stakingContract.unstake(userB.address, 200); - txs[0] = await stakingContract.unstake(userA.address, 400); - tx = await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await local.syncBalance(); - let lastCreditedB = local.aRps.mul(local.balanceB).div(MASK); - let lastCreditedA = local.aRps.mul(local.balanceA).div(MASK); - await expect(txs[1]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userB.address, local.accumulatedRewardForB, lastCreditedB); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 3725, lastCreditedA); - await local.recordReward(1000); - await expect(tx!).to.emit(stakingContract, 'PendingPoolUpdated').withArgs(poolAddr, local.aRps); - await expectLocalCalculationRight(); - - txs[0] = await stakingContract.claimReward(userA.address); - txs[1] = await stakingContract.claimReward(userB.address); - await network.provider.send('evm_mine'); - lastCreditedA = lastCreditedA.add(local.claimableRewardForA); - await expect(txs[0]!) - .emit(stakingContract, 'RewardClaimed') - .withArgs(poolAddr, userA.address, local.claimableRewardForA); - await expect(txs[0]!) - .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userA.address, 0, local.settledARps); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 3725, lastCreditedA); - lastCreditedB = lastCreditedB.add(local.claimableRewardForB); - await expect(txs[1]!) - .emit(stakingContract, 'RewardClaimed') - .withArgs(poolAddr, userB.address, local.claimableRewardForB); - await expect(txs[1]!) - .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userB.address, 0, local.settledARps); - await expect(txs[1]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userB.address, 1275, lastCreditedB); - local.claimRewardForA(); - local.claimRewardForB(); - await expectLocalCalculationRight(); - - txs[0] = await stakingContract.unstake(userA.address, 200); - tx = await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await network.provider.send('evm_mine'); - await local.syncBalance(); - lastCreditedA = local.balanceA.mul(local.aRps).div(MASK); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, local.accumulatedRewardForA, lastCreditedA); - local.settledPools(); - await StakingContract.expects.emitSettledPoolsUpdatedEvent(tx!, [poolAddr], [local.settledARps]); - await expectLocalCalculationRight(); - - txs[0] = await stakingContract.claimReward(userA.address); - txs[1] = await stakingContract.claimReward(userB.address); - await network.provider.send('evm_mine'); - lastCreditedA = lastCreditedA.add(local.claimableRewardForA); - await expect(txs[0]!) - .emit(stakingContract, 'RewardClaimed') - .withArgs(poolAddr, userA.address, local.claimableRewardForA); - await expect(txs[0]!) - .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userA.address, 0, local.settledARps); - await expect(txs[0]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userA.address, 3725, lastCreditedA); - lastCreditedB = lastCreditedB.add(local.claimableRewardForB); - await expect(txs[1]!) - .emit(stakingContract, 'RewardClaimed') - .withArgs(poolAddr, userB.address, local.claimableRewardForB); - await expect(txs[1]!) - .emit(stakingContract, 'SettledRewardUpdated') - .withArgs(poolAddr, userB.address, 0, local.settledARps); - await expect(txs[1]!) - .emit(stakingContract, 'PendingRewardUpdated') - .withArgs(poolAddr, userB.address, 1275, lastCreditedB); - local.claimRewardForA(); - local.claimRewardForB(); - await expectLocalCalculationRight(); - }); - - it('Should work properly with staking actions occurring in the same block', async () => { - await stakingContract.stake(userA.address, 100); - await stakingContract.unstake(userA.address, 100); - await stakingContract.stake(userA.address, 100); - await stakingContract.stake(userB.address, 200); - await stakingContract.unstake(userB.address, 200); - await stakingContract.recordReward(1000); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - local.settledPools(); - await expectLocalCalculationRight(); - - await stakingContract.recordReward(1000); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - await expectLocalCalculationRight(); - - await stakingContract.slash(); - await network.provider.send('evm_mine'); - local.slash(); - await expectLocalCalculationRight(); - - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await network.provider.send('evm_mine'); - await expectLocalCalculationRight(); - - await stakingContract.recordReward(1000); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - local.settledPools(); - await expectLocalCalculationRight(); - - await stakingContract.recordReward(1000); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await network.provider.send('evm_mine'); - await local.recordReward(1000); - local.settledPools(); - await expectLocalCalculationRight(); - - await stakingContract.claimReward(userA.address); - await stakingContract.claimReward(userA.address); - await stakingContract.claimReward(userB.address); - await stakingContract.claimReward(userB.address); - await stakingContract.claimReward(userB.address); - await network.provider.send('evm_mine'); - local.claimRewardForA(); - local.claimRewardForB(); - await expectLocalCalculationRight(); - }); -}); diff --git a/test/staking/RewardAmountCalculation.test.ts b/test/staking/RewardAmountCalculation.test.ts deleted file mode 100644 index 04bddebae..000000000 --- a/test/staking/RewardAmountCalculation.test.ts +++ /dev/null @@ -1,327 +0,0 @@ -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { expect } from 'chai'; -import { ethers } from 'hardhat'; - -import { MockStaking, MockStaking__factory } from '../../src/types'; - -const poolAddr = ethers.constants.AddressZero; - -let deployer: SignerWithAddress; -let userA: SignerWithAddress; -let userB: SignerWithAddress; -let stakingContract: MockStaking; - -const setupNormalCase = async (stakingContract: MockStaking) => { - await stakingContract.stake(userA.address, 100); - await stakingContract.stake(userB.address, 100); - await stakingContract.recordReward(1000); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); -}; - -const expectPendingRewards = async (expectingA: number, expectingB: number) => { - expect(await stakingContract.getPendingReward(poolAddr, userA.address)).eq(expectingA); - expect(await stakingContract.getPendingReward(poolAddr, userB.address)).eq(expectingB); -}; - -const expectClaimableRewards = async (expectingA: number, expectingB: number) => { - expect(await stakingContract.getClaimableReward(poolAddr, userA.address)).eq(expectingA); - expect(await stakingContract.getClaimableReward(poolAddr, userB.address)).eq(expectingB); -}; - -describe('Claimable/Pending Reward Calculation test', () => { - before(async () => { - [deployer, userA, userB] = await ethers.getSigners(); - stakingContract = await new MockStaking__factory(deployer).deploy(poolAddr); - }); - - it('Should calculate correctly the claimable reward in the normal case', async () => { - await setupNormalCase(stakingContract); - await expectClaimableRewards(500, 500); - await expectPendingRewards(0, 0); - }); - - describe('Interaction with a pool that will be settled', async () => { - describe('One interaction per period', async () => { - before(async () => { - stakingContract = await new MockStaking__factory(deployer).deploy(poolAddr); - await setupNormalCase(stakingContract); - }); - - it('Should the claimable reward not change when the user interacts in the pending period', async () => { - await stakingContract.stake(userA.address, 200); - await stakingContract.recordReward(1000); - await expectClaimableRewards(500, 500); - await expectPendingRewards(750, 250); - }); - - it('Should the claimable reward increase when the pool is settled', async () => { - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 750, 500 + 250); - await expectPendingRewards(0, 0); - }); - - it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 750, 500 + 250); - await expectPendingRewards(0, 0); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 750, 500 + 250); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 750, 500 + 250); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 750, 500 + 250); - await expectPendingRewards(0, 0); - }); - - it('Should the claimable reward increase when the pool is settled', async () => { - await stakingContract.recordReward(1000); - await expectPendingRewards(750, 250); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 750 + 750, 500 + 250 + 250); - await expectPendingRewards(0, 0); - }); - - it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 750 + 750, 500 + 250 + 250); - await expectPendingRewards(0, 0); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 750 + 750, 500 + 250 + 250); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 750 + 750, 500 + 250 + 250); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 750 + 750, 500 + 250 + 250); - await expectPendingRewards(0, 0); - }); - }); - - describe('Many interactions per period', async () => { - before(async () => { - stakingContract = await new MockStaking__factory(deployer).deploy(poolAddr); - await setupNormalCase(stakingContract); - }); - - it('Should the claimable reward not change when the user interacts in the pending period', async () => { - await stakingContract.stake(userA.address, 200); - await stakingContract.stake(userA.address, 500); - await stakingContract.stake(userA.address, 100); - await stakingContract.recordReward(1000); - await stakingContract.stake(userA.address, 800); - await expectClaimableRewards(500, 500); - await expectPendingRewards(900, 100); - }); - - it('Should the claimable reward increase when the pool is settled', async () => { - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900, 500 + 100); - }); - - it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { - await stakingContract.slash(); - await stakingContract.unstake(userA.address, 800); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900, 500 + 100); - await expectPendingRewards(0, 0); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900, 500 + 100); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900, 500 + 100); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900, 500 + 100); - await expectPendingRewards(0, 0); - }); - - it('Should the claimable reward increase when the pool is settled', async () => { - await stakingContract.recordReward(1000); - await expectPendingRewards(900, 100); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900 + 900, 500 + 100 + 100); - await expectPendingRewards(0, 0); - }); - - it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900 + 900, 500 + 100 + 100); - await expectPendingRewards(0, 0); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900 + 900, 500 + 100 + 100); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900 + 900, 500 + 100 + 100); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900 + 900, 500 + 100 + 100); - await expectPendingRewards(0, 0); - }); - }); - }); - - describe('Interaction with a pool that will be slashed', async () => { - describe('One interaction per period', async () => { - before(async () => { - stakingContract = await new MockStaking__factory(deployer).deploy(poolAddr); - await setupNormalCase(stakingContract); - }); - - it('Should the claimable reward not change when the user interacts in the pending period', async () => { - await stakingContract.stake(userA.address, 200); - await stakingContract.recordReward(1000); - await stakingContract.stake(userA.address, 600); - await expectClaimableRewards(500, 500); - await expectPendingRewards(750, 250); - }); - - it('Should the claimable reward not change when the pool is slashed', async () => { - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500, 500); - await expectPendingRewards(0, 0); - }); - - it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500, 500); - await expectPendingRewards(0, 0); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await expectClaimableRewards(500, 500); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500, 500); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500, 500); - await expectPendingRewards(0, 0); - }); - - it('Should the claimable reward increase when the pool records reward', async () => { - await stakingContract.recordReward(1000); - await expectPendingRewards(900, 100); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900, 500 + 100); - await expectPendingRewards(0, 0); - }); - - it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900, 500 + 100); - await expectPendingRewards(0, 0); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900, 500 + 100); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900, 500 + 100); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900, 500 + 100); - await expectPendingRewards(0, 0); - }); - }); - - describe('Many interactions per period', async () => { - before(async () => { - stakingContract = await new MockStaking__factory(deployer).deploy(poolAddr); - await setupNormalCase(stakingContract); - }); - - it('Should the claimable reward not change when the user interacts in the pending period', async () => { - await stakingContract.stake(userA.address, 200); - await stakingContract.unstake(userA.address, 150); - await stakingContract.unstake(userA.address, 50); - await stakingContract.recordReward(1000); - await stakingContract.stake(userA.address, 500); - await stakingContract.stake(userA.address, 300); - await expectClaimableRewards(500, 500); - await expectPendingRewards(500, 500); - }); - - it('Should the claimable reward not change when the pool is slashed', async () => { - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500, 500); - await expectPendingRewards(0, 0); - }); - - it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500, 500); - await expectPendingRewards(0, 0); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await expectClaimableRewards(500, 500); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500, 500); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500, 500); - await expectPendingRewards(0, 0); - }); - - it('Should the claimable reward increase when the pool records reward', async () => { - await stakingContract.recordReward(1000); - await expectPendingRewards(900, 100); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900, 500 + 100); - await expectPendingRewards(0, 0); - }); - - it('Should the claimable reward be still, no matter whether the pool is slashed or settled', async () => { - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900, 500 + 100); - await expectPendingRewards(0, 0); - await stakingContract.settledPools([poolAddr]); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900, 500 + 100); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900, 500 + 100); - await expectPendingRewards(0, 0); - await stakingContract.slash(); - await stakingContract.endPeriod(); - await expectClaimableRewards(500 + 900, 500 + 100); - await expectPendingRewards(0, 0); - }); - }); - }); -}); diff --git a/test/staking/RewardCalculation.test.ts b/test/staking/RewardCalculation.test.ts new file mode 100644 index 000000000..041e404e0 --- /dev/null +++ b/test/staking/RewardCalculation.test.ts @@ -0,0 +1,221 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { BigNumber, ContractTransaction } from 'ethers'; +import { ethers } from 'hardhat'; +import { expect } from 'chai'; + +import { MockStaking, MockStaking__factory } from '../../src/types'; + +const MASK = BigNumber.from(10).pow(18); +const poolAddr = ethers.constants.AddressZero; + +let period = 1; +let deployer: SignerWithAddress; +let userA: SignerWithAddress; +let userB: SignerWithAddress; +let stakingContract: MockStaking; +let aRps: BigNumber; + +describe('Reward Calculation test', () => { + let tx: ContractTransaction; + const txs: ContractTransaction[] = []; + + before(async () => { + [deployer, userA, userB] = await ethers.getSigners(); + stakingContract = await new MockStaking__factory(deployer).deploy(poolAddr); + }); + + describe('Before the first wrap up', () => { + it('Should be able to stake/unstake before the first period', async () => { + txs[0] = await stakingContract.stake(userA.address, 500); + await expect(txs[0]).not.emit(stakingContract, 'UserRewardUpdated'); + txs[0] = await stakingContract.unstake(userA.address, 450); + await expect(txs[0]).not.emit(stakingContract, 'UserRewardUpdated'); + txs[0] = await stakingContract.stake(userA.address, 50); + await expect(txs[0]).not.emit(stakingContract, 'UserRewardUpdated'); + expect(await stakingContract.stakingAmountOf(poolAddr, userA.address)).eq(100); + }); + + it('Should be able to wrap up period for the first period', async () => { + await stakingContract.firstEverWrapup(); + period = (await stakingContract.lastUpdatedPeriod()).toNumber(); + }); + }); + + describe('Period: x+0 -> x+1', () => { + it('Should be able to unstake/stake at the first period', async () => { + txs[0] = await stakingContract.unstake(userA.address, 50); + await expect(txs[0]).not.emit(stakingContract, 'UserRewardUpdated'); + await expect(txs[0]).emit(stakingContract, 'PoolSharesUpdated').withArgs(period, poolAddr, 50); + txs[0] = await stakingContract.stake(userA.address, 50); + await expect(txs[0]).not.emit(stakingContract, 'UserRewardUpdated'); + expect(await stakingContract.stakingAmountOf(poolAddr, userA.address)).eq(100); + }); + + it('Should be able to record reward for the pool', async () => { + await stakingContract.increaseReward(1000); + await stakingContract.decreaseReward(500); + aRps = MASK.mul(500 / 50); + tx = await stakingContract.endPeriod(); // period = 1 + await expect(tx) + .emit(stakingContract, 'PoolsUpdated') + .withArgs(period++, [poolAddr], [aRps], [100]); + expect(await stakingContract.getReward(poolAddr, userA.address)).eq(500); + }); + }); + + describe('Period: x+1 -> x+2', () => { + it('Should not be able to record reward with invalid arguments', async () => { + tx = await stakingContract.recordRewards([poolAddr], [100, 100]); + await expect(tx).emit(stakingContract, 'PoolsUpdateFailed').withArgs(period, [poolAddr], [100, 100]); + }); + + it('Should not be able to record reward more than once for a pool', async () => { + aRps = aRps.add(MASK.mul(1000 / 100)); + tx = await stakingContract.recordRewards([poolAddr], [1000]); + await expect(tx).emit(stakingContract, 'PoolsUpdated').withArgs(period, [poolAddr], [aRps], [100]); + tx = await stakingContract.recordRewards([poolAddr], [1000]); + await expect(tx).emit(stakingContract, 'PoolUpdateConflicted').withArgs(period, poolAddr); + await stakingContract.increasePeriod(); // period = 2 + period++; + expect(await stakingContract.getReward(poolAddr, userA.address)).eq(1500); // 1000 + 500 from the last period + }); + }); + + describe('Period: x+2 -> x+3', () => { + it('Should be able to change the staking amount and the reward moved into the debited part', async () => { + txs[0] = await stakingContract.stake(userA.address, 200); + await expect(txs[0]).emit(stakingContract, 'UserRewardUpdated').withArgs(poolAddr, userA.address, 1500); + txs[0] = await stakingContract.unstake(userA.address, 100); + await expect(txs[0]).not.emit(stakingContract, 'UserRewardUpdated'); + }); + + it('Should be able to claim the earned reward', async () => { + txs[0] = await stakingContract.claimReward(userA.address); + await expect(txs[0]).emit(stakingContract, 'RewardClaimed').withArgs(poolAddr, userA.address, 1500); + await expect(txs[0]).emit(stakingContract, 'UserRewardUpdated').withArgs(poolAddr, userA.address, 0); + }); + + it('Should be able to change the staking amount and the debited part is empty', async () => { + txs[0] = await stakingContract.stake(userA.address, 200); + await expect(txs[0]).not.emit(stakingContract, 'UserRewardUpdated'); + txs[0] = await stakingContract.unstake(userA.address, 350); + await expect(txs[0]).not.emit(stakingContract, 'UserRewardUpdated'); + await expect(txs[0]).emit(stakingContract, 'PoolSharesUpdated').withArgs(period, poolAddr, 50); + expect(await stakingContract.stakingAmountOf(poolAddr, userA.address)).eq(50); + txs[0] = await stakingContract.stake(userA.address, 250); + await expect(txs[0]).not.emit(stakingContract, 'UserRewardUpdated'); + + txs[1] = await stakingContract.stake(userB.address, 200); + await expect(txs[1]).not.emit(stakingContract, 'UserRewardUpdated'); + expect(await stakingContract.stakingAmountOf(poolAddr, userB.address)).eq(200); + }); + + it('Should be able to distribute reward based on the smallest amount in the last period', async () => { + aRps = aRps.add(MASK.mul(1000 / 50)); + await stakingContract.increaseReward(1000); + tx = await stakingContract.endPeriod(); // period = 3 + expect(await stakingContract.getReward(poolAddr, userA.address)).eq(1000); + await expect(tx) + .emit(stakingContract, 'PoolsUpdated') + .withArgs(period++, [poolAddr], [aRps], [await stakingContract.stakingTotal(poolAddr)]); + }); + }); + + describe('Period: x+3 -> x+10', () => { + it('Should be able to get right reward', async () => { + aRps = aRps.add(MASK.mul(1000 / 500)); + await stakingContract.increaseReward(1000); + tx = await stakingContract.endPeriod(); // period 4 + await expect(tx) + .emit(stakingContract, 'PoolsUpdated') + .withArgs(period++, [poolAddr], [aRps], [500]); + + expect(await stakingContract.getReward(poolAddr, userA.address)).eq(1600); // 3/5 of 1000 + 1000 from the last period + expect(await stakingContract.getReward(poolAddr, userB.address)).eq(400); // 2/5 of 1000 + }); + + it('Should be able to unstake and receives reward based on the smallest amount in the last period', async () => { + txs[0] = await stakingContract.unstake(userA.address, 250); + await expect(txs[0]).emit(stakingContract, 'UserRewardUpdated').withArgs(poolAddr, userA.address, 1600); + await expect(txs[0]).emit(stakingContract, 'PoolSharesUpdated').withArgs(period, poolAddr, 250); + expect(await stakingContract.stakingAmountOf(poolAddr, userA.address)).eq(50); + + txs[1] = await stakingContract.unstake(userB.address, 150); + await expect(txs[1]).emit(stakingContract, 'UserRewardUpdated').withArgs(poolAddr, userB.address, 400); + await expect(txs[1]).emit(stakingContract, 'PoolSharesUpdated').withArgs(period, poolAddr, 100); + expect(await stakingContract.stakingAmountOf(poolAddr, userB.address)).eq(50); + + aRps = aRps.add(MASK.mul(1000 / 100)); + await stakingContract.increaseReward(1000); + tx = await stakingContract.endPeriod(); // period 5 + await expect(tx) + .emit(stakingContract, 'PoolsUpdated') + .withArgs(period++, [poolAddr], [aRps], [await stakingContract.stakingTotal(poolAddr)]); + expect(await stakingContract.getReward(poolAddr, userA.address)).eq(2100); // 50% of 1000 + 1600 from the last period + expect(await stakingContract.getReward(poolAddr, userB.address)).eq(900); // 50% of 1000 + 400 from the last period + }); + + it('Should not distribute reward for the ones who unstake all in the period', async () => { + txs[1] = await stakingContract.unstake(userB.address, 50); + await expect(txs[1]).emit(stakingContract, 'PoolSharesUpdated').withArgs(period, poolAddr, 50); + await expect(txs[1]).emit(stakingContract, 'UserRewardUpdated').withArgs(poolAddr, userB.address, 900); + expect(await stakingContract.stakingAmountOf(poolAddr, userB.address)).eq(0); + + aRps = aRps.add(MASK.mul(1000 / 50)); + await stakingContract.increaseReward(1000); + tx = await stakingContract.endPeriod(); // period 6 + await expect(tx) + .emit(stakingContract, 'PoolsUpdated') + .withArgs(period++, [poolAddr], [aRps], [await stakingContract.stakingTotal(poolAddr)]); + expect(await stakingContract.getReward(poolAddr, userA.address)).eq(3100); // 1000 + 2100 from the last period + }); + + it('The pool should be fine when no one stakes', async () => { + txs[0] = await stakingContract.unstake(userA.address, 50); + await expect(txs[0]).emit(stakingContract, 'PoolSharesUpdated').withArgs(period, poolAddr, 0); + await expect(txs[0]).emit(stakingContract, 'UserRewardUpdated').withArgs(poolAddr, userA.address, 3100); + expect(await stakingContract.stakingAmountOf(poolAddr, userA.address)).eq(0); + expect(await stakingContract.stakingAmountOf(poolAddr, userB.address)).eq(0); + + aRps = aRps.add(0); + await stakingContract.increaseReward(1000); + tx = await stakingContract.endPeriod(); // period 7 + await expect(tx) + .emit(stakingContract, 'PoolsUpdated') + .withArgs(period++, [poolAddr], [aRps], [0]); + expect(await stakingContract.getReward(poolAddr, userA.address)).eq(3100); + expect(await stakingContract.getReward(poolAddr, userB.address)).eq(900); + }); + + it('The rewards should be still when the pool has no reward for multi periods', async () => { + tx = await stakingContract.endPeriod(); // period 8 + await expect(tx) + .emit(stakingContract, 'PoolsUpdated') + .withArgs(period++, [poolAddr], [aRps], [0]); + tx = await stakingContract.endPeriod(); // period 9 + await expect(tx) + .emit(stakingContract, 'PoolsUpdated') + .withArgs(period++, [poolAddr], [aRps], [0]); + tx = await stakingContract.endPeriod(); // period 10 + await expect(tx) + .emit(stakingContract, 'PoolsUpdated') + .withArgs(period++, [poolAddr], [aRps], [0]); + expect(await stakingContract.getReward(poolAddr, userA.address)).eq(3100); + expect(await stakingContract.getReward(poolAddr, userB.address)).eq(900); + }); + + it('Should be able to claim reward after all', async () => { + txs[0] = await stakingContract.unstake(userA.address, 0); + await expect(txs[0]).not.emit(stakingContract, 'UserRewardUpdated'); + txs[0] = await stakingContract.claimReward(userA.address); + await expect(txs[0]).emit(stakingContract, 'RewardClaimed').withArgs(poolAddr, userA.address, 3100); + await expect(txs[0]).emit(stakingContract, 'UserRewardUpdated').withArgs(poolAddr, userA.address, 0); + + txs[1] = await stakingContract.claimReward(userB.address); + await expect(txs[1]).emit(stakingContract, 'RewardClaimed').withArgs(poolAddr, userB.address, 900); + await expect(txs[1]).emit(stakingContract, 'UserRewardUpdated').withArgs(poolAddr, userB.address, 0); + txs[1] = await stakingContract.unstake(userA.address, 0); + await expect(txs[1]).not.emit(stakingContract, 'UserRewardUpdated'); + }); + }); +}); diff --git a/test/staking/Staking.test.ts b/test/staking/Staking.test.ts index 4285f9101..0af2b3f8c 100644 --- a/test/staking/Staking.test.ts +++ b/test/staking/Staking.test.ts @@ -18,7 +18,7 @@ let validatorContract: MockValidatorSet; let stakingContract: Staking; let validatorCandidates: SignerWithAddress[]; -const minValidatorBalance = BigNumber.from(20); +const minValidatorStakingAmount = BigNumber.from(20); const maxValidatorCandidate = 50; const numberOfBlocksInEpoch = 2; @@ -42,7 +42,7 @@ describe('Staking test', () => { const proxyContract = await new TransparentUpgradeableProxyV2__factory(deployer).deploy( logicContract.address, proxyAdmin.address, - logicContract.interface.encodeFunctionData('initialize', [validatorContract.address, minValidatorBalance]) + logicContract.interface.encodeFunctionData('initialize', [validatorContract.address, minValidatorStakingAmount]) ); await proxyContract.deployed(); stakingContract = Staking__factory.connect(proxyContract.address, deployer); @@ -67,13 +67,13 @@ describe('Staking test', () => { candidate.address, candidate.address, 1, - /* 0.01% */ { value: minValidatorBalance.mul(2) } + /* 0.01% */ { value: minValidatorStakingAmount.mul(2) } ); await expect(tx).emit(stakingContract, 'PoolApproved').withArgs(candidate.address, candidate.address); } poolAddr = validatorCandidates[1]; - expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance.mul(2)); + expect(await stakingContract.stakingTotal(poolAddr.address)).eq(minValidatorStakingAmount.mul(2)); }); it('Should not be able to propose validator again', async () => { @@ -81,7 +81,7 @@ describe('Staking test', () => { stakingContract .connect(poolAddr) .applyValidatorCandidate(poolAddr.address, poolAddr.address, poolAddr.address, poolAddr.address, 0, { - value: minValidatorBalance, + value: minValidatorStakingAmount, }) ).revertedWith('CandidateManager: query for already existent candidate'); }); @@ -106,25 +106,25 @@ describe('Staking test', () => { let tx: ContractTransaction; tx = await stakingContract.connect(poolAddr).stake(poolAddr.address, { value: 1 }); await expect(tx!).emit(stakingContract, 'Staked').withArgs(poolAddr.address, 1); - expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance.mul(2).add(1)); + expect(await stakingContract.stakingTotal(poolAddr.address)).eq(minValidatorStakingAmount.mul(2).add(1)); tx = await stakingContract.connect(poolAddr).unstake(poolAddr.address, 1); await expect(tx!).emit(stakingContract, 'Unstaked').withArgs(poolAddr.address, 1); - expect(await stakingContract.totalBalance(poolAddr.address)).eq(minValidatorBalance.mul(2)); - expect(await stakingContract.balanceOf(poolAddr.address, poolAddr.address)).eq(minValidatorBalance.mul(2)); + expect(await stakingContract.stakingTotal(poolAddr.address)).eq(minValidatorStakingAmount.mul(2)); + expect(await stakingContract.stakingAmountOf(poolAddr.address, poolAddr.address)).eq(minValidatorStakingAmount.mul(2)); }); it('[Delegator] Should be able to delegate/undelegate to a validator candidate', async () => { await stakingContract.delegate(poolAddr.address, { value: 10 }); - expect(await stakingContract.balanceOf(poolAddr.address, deployer.address)).eq(10); + expect(await stakingContract.stakingAmountOf(poolAddr.address, deployer.address)).eq(10); await stakingContract.undelegate(poolAddr.address, 1); - expect(await stakingContract.balanceOf(poolAddr.address, deployer.address)).eq(9); + expect(await stakingContract.stakingAmountOf(poolAddr.address, deployer.address)).eq(9); }); it('Should be not able to unstake with the balance left is not larger than the minimum balance threshold', async () => { await expect( - stakingContract.connect(poolAddr).unstake(poolAddr.address, minValidatorBalance.add(1)) - ).revertedWith('StakingManager: invalid staked amount left'); + stakingContract.connect(poolAddr).unstake(poolAddr.address, minValidatorStakingAmount.add(1)) + ).revertedWith('StakingManager: invalid staking amount left'); }); it('Should not be able to request renounce using unauthorized account', async () => { @@ -149,13 +149,14 @@ describe('Staking test', () => { ethers.utils.hexStripZeros(BigNumber.from(numberOfBlocksInEpoch).toHexString()), '0x0', ]); - const stakedAmount = minValidatorBalance.mul(2); + const stakingAmount = minValidatorStakingAmount.mul(2); expect(await stakingContract.getStakingPool(poolAddr.address)).eql([ poolAddr.address, - stakedAmount, - stakedAmount.add(9), + stakingAmount, + stakingAmount.add(9), ]); - await expect(() => validatorContract.wrapUpEpoch()).changeEtherBalance(poolAddr, stakedAmount); + + await expect(() => validatorContract.wrapUpEpoch()).changeEtherBalance(poolAddr, stakingAmount); await expect(stakingContract.getStakingPool(poolAddr.address)).revertedWith( 'StakingManager: query for non-existent pool' ); @@ -169,7 +170,7 @@ describe('Staking test', () => { it('Should be able to undelegate from a deprecated validator candidate', async () => { await stakingContract.undelegate(poolAddr.address, 1); - expect(await stakingContract.balanceOf(poolAddr.address, deployer.address)).eq(8); + expect(await stakingContract.stakingAmountOf(poolAddr.address, deployer.address)).eq(8); }); it('Should not be able to delegate to a deprecated pool', async () => { @@ -201,18 +202,18 @@ describe('Staking test', () => { tx = await stakingContract.connect(userB).delegate(otherPoolAddr.address, { value: 1 }); await expect(tx!).emit(stakingContract, 'Delegated').withArgs(userB.address, otherPoolAddr.address, 1); - expect(await stakingContract.totalBalance(otherPoolAddr.address)).eq(minValidatorBalance.mul(2).add(2)); + expect(await stakingContract.stakingTotal(otherPoolAddr.address)).eq(minValidatorStakingAmount.mul(2).add(2)); tx = await stakingContract.connect(userA).undelegate(otherPoolAddr.address, 1); await expect(tx!).emit(stakingContract, 'Undelegated').withArgs(userA.address, otherPoolAddr.address, 1); - expect(await stakingContract.totalBalance(otherPoolAddr.address)).eq(minValidatorBalance.mul(2).add(1)); + expect(await stakingContract.stakingTotal(otherPoolAddr.address)).eq(minValidatorStakingAmount.mul(2).add(1)); }); it('Should not be able to undelegate with empty amount', async () => { await expect(stakingContract.undelegate(otherPoolAddr.address, 0)).revertedWith('StakingManager: invalid amount'); }); - it('Should not be able to undelegate more than the delegated amount', async () => { + it('Should not be able to undelegate more than the delegating amount', async () => { await expect(stakingContract.undelegate(otherPoolAddr.address, 1000)).revertedWith( 'StakingManager: insufficient amount to undelegate' ); @@ -227,30 +228,30 @@ describe('Staking test', () => { poolAddr.address, poolAddr.address, 2, - /* 0.02% */ { value: minValidatorBalance } + /* 0.02% */ { value: minValidatorStakingAmount } ); expect(await stakingContract.getStakingPool(poolAddr.address)).eql([ poolAddr.address, - minValidatorBalance, - minValidatorBalance.add(8), + minValidatorStakingAmount, + minValidatorStakingAmount.add(8), ]); - expect(await stakingContract.balanceOf(poolAddr.address, deployer.address)).eq(8); + expect(await stakingContract.stakingAmountOf(poolAddr.address, deployer.address)).eq(8); }); it('Should be able to delegate/undelegate for the rejoined candidate', async () => { await stakingContract.delegate(poolAddr.address, { value: 2 }); - expect(await stakingContract.balanceOf(poolAddr.address, deployer.address)).eq(10); + expect(await stakingContract.stakingAmountOf(poolAddr.address, deployer.address)).eq(10); await stakingContract.connect(userA).delegate(poolAddr.address, { value: 2 }); await stakingContract.connect(userB).delegate(poolAddr.address, { value: 2 }); expect( - await stakingContract.bulkBalanceOf([poolAddr.address, poolAddr.address], [userA.address, userB.address]) + await stakingContract.bulkStakingAmountOf([poolAddr.address, poolAddr.address], [userA.address, userB.address]) ).eql([2, 2].map(BigNumber.from)); await stakingContract.connect(userA).undelegate(poolAddr.address, 2); await stakingContract.connect(userB).undelegate(poolAddr.address, 1); expect( - await stakingContract.bulkBalanceOf([poolAddr.address, poolAddr.address], [userA.address, userB.address]) + await stakingContract.bulkStakingAmountOf([poolAddr.address, poolAddr.address], [userA.address, userB.address]) ).eql([0, 1].map(BigNumber.from)); }); }); diff --git a/test/validator/RoninValidatorSet.test.ts b/test/validator/RoninValidatorSet.test.ts index 310617b8b..3043942b7 100644 --- a/test/validator/RoninValidatorSet.test.ts +++ b/test/validator/RoninValidatorSet.test.ts @@ -44,7 +44,7 @@ const localValidatorCandidatesLength = 5; const slashAmountForUnavailabilityTier2Threshold = 100; const maxValidatorNumber = 4; const maxValidatorCandidate = 100; -const minValidatorBalance = BigNumber.from(20000); +const minValidatorStakingAmount = BigNumber.from(20000); const blockProducerBonusPerBlock = BigNumber.from(5000); const bridgeOperatorBonusPerBlock = BigNumber.from(37); const zeroTopUpAmount = 0; @@ -69,7 +69,7 @@ describe('Ronin Validator Set test', () => { }, }, stakingArguments: { - minValidatorBalance, + minValidatorStakingAmount, }, stakingVestingArguments: { blockProducerBonusPerBlock, @@ -151,7 +151,7 @@ describe('Ronin Validator Set test', () => { validatorCandidates[i].address, 2_00, { - value: minValidatorBalance.add(i), + value: minValidatorStakingAmount.add(i), } ); } @@ -213,7 +213,7 @@ describe('Ronin Validator Set test', () => { bridgeOperator.address, 1_00 /* 1% */, { - value: minValidatorBalance.mul(100), + value: minValidatorStakingAmount.mul(100), } ); for (let i = 4; i < localValidatorCandidatesLength; i++) { @@ -226,7 +226,7 @@ describe('Ronin Validator Set test', () => { validatorCandidates[i].address, 2_00, { - value: minValidatorBalance.add(i), + value: minValidatorStakingAmount.add(i), } ); } @@ -339,7 +339,7 @@ describe('Ronin Validator Set test', () => { ); const balanceDiff = (await treasury.getBalance()).sub(balance); expect(balanceDiff).eq(61); // = (5000 + 100 + 100) * 1% + 9 = (52 + 9) - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + expect(await stakingContract.getReward(coinbase.address, coinbase.address)).eq( 5148 // (5000 + 100 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase ); }); @@ -361,7 +361,7 @@ describe('Ronin Validator Set test', () => { const balanceDiff = (await treasury.getBalance()).sub(balance); expect(balanceDiff).eq(0); // The delegators don't receives the new rewards until the period is ended - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + expect(await stakingContract.getReward(coinbase.address, coinbase.address)).eq( 5148 // (5000 + 100 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase ); await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, false); @@ -383,7 +383,7 @@ describe('Ronin Validator Set test', () => { const balanceDiff = (await treasury.getBalance()).sub(balance); const totalBridgeReward = bridgeOperatorBonusPerBlock.mul(2); // called submitBlockReward 2 times expect(balanceDiff).eq(totalBridgeReward.div(await roninValidatorSet.totalBlockProducers())); - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + expect(await stakingContract.getReward(coinbase.address, coinbase.address)).eq( 5148 // (5000 + 100 + 100) * 99% = 99% of the reward, since the pool is only staked by the coinbase ); await expect(tx!).emit(roninValidatorSet, 'WrappedUpEpoch').withArgs(lastPeriod, epoch, true); @@ -422,7 +422,7 @@ describe('Ronin Validator Set test', () => { let _rewardFromBonus = blockProducerBonusPerBlock.div(100).mul(99).mul(2); let _rewardFromSubmission = BigNumber.from(100).div(100).mul(99).mul(3); - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + expect(await stakingContract.getReward(coinbase.address, coinbase.address)).eq( _rewardFromBonus.add(_rewardFromSubmission) ); }); @@ -447,7 +447,7 @@ describe('Ronin Validator Set test', () => { let _rewardFromBonus = blockProducerBonusPerBlock.div(100).mul(99).mul(2); let _rewardFromSubmission = BigNumber.from(100).div(100).mul(99).mul(3); - expect(await stakingContract.getClaimableReward(coinbase.address, coinbase.address)).eq( + expect(await stakingContract.getReward(coinbase.address, coinbase.address)).eq( _rewardFromBonus.add(_rewardFromSubmission) ); From 4ea5137c101a9dd0399925c34d69c65fa174235c Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Fri, 4 Nov 2022 14:30:49 +0700 Subject: [PATCH 189/190] Optimize slashing method in contracts/ronin/validator/RoninValidatorSet.sol --- contracts/ronin/validator/RoninValidatorSet.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/ronin/validator/RoninValidatorSet.sol b/contracts/ronin/validator/RoninValidatorSet.sol index 19be96835..dfaec1839 100644 --- a/contracts/ronin/validator/RoninValidatorSet.sol +++ b/contracts/ronin/validator/RoninValidatorSet.sol @@ -211,8 +211,8 @@ contract RoninValidatorSet is delete _miningReward[_validatorAddr]; delete _delegatingReward[_validatorAddr]; - if (_newJailedUntil > 0) { - _jailedUntil[_validatorAddr] = Math.max(_newJailedUntil, _jailedUntil[_validatorAddr]); + if (_newJailedUntil > _jailedUntil[_validatorAddr]) { + _jailedUntil[_validatorAddr] = _newJailedUntil; } if (_slashAmount > 0) { From 603d7f6df260d2f86e9f90e8c5be536f933698cc Mon Sep 17 00:00:00 2001 From: Duc Tho Tran Date: Fri, 4 Nov 2022 14:43:33 +0700 Subject: [PATCH 190/190] Add reserved space for future upgrade --- contracts/ronin/staking/RewardCalculation.sol | 6 ++++++ contracts/ronin/staking/StakingManager.sol | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/contracts/ronin/staking/RewardCalculation.sol b/contracts/ronin/staking/RewardCalculation.sol index c32e0c659..1b66af01e 100644 --- a/contracts/ronin/staking/RewardCalculation.sol +++ b/contracts/ronin/staking/RewardCalculation.sol @@ -17,6 +17,12 @@ abstract contract RewardCalculation is IRewardPool { /// @dev Mapping from the pool address => reward pool fields mapping(address => PoolFields) private _stakingPool; + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + */ + uint256[50] private ______gap; + /** * @inheritdoc IRewardPool */ diff --git a/contracts/ronin/staking/StakingManager.sol b/contracts/ronin/staking/StakingManager.sol index e5c50f1ab..efab0d0cd 100644 --- a/contracts/ronin/staking/StakingManager.sol +++ b/contracts/ronin/staking/StakingManager.sol @@ -19,6 +19,12 @@ abstract contract StakingManager is /// @dev Mapping from pool address => staking pool detail mapping(address => PoolDetail) internal _stakingPool; + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + */ + uint256[50] private ______gap; + modifier noEmptyValue() { require(msg.value > 0, "StakingManager: query with empty value"); _;