diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml new file mode 100644 index 00000000..47f24e11 --- /dev/null +++ b/.github/workflows/manual.yml @@ -0,0 +1,30 @@ +# This is a basic workflow that is manually triggered + +name: Manual workflow + +# Controls when the action will run. Workflow runs when manually triggered using the UI +# or API. +on: + workflow_dispatch: + # Inputs the workflow accepts. + inputs: + name: + # Friendly description to be shown in the UI instead of 'name' + description: 'Person to greet' + # Default value if no value is explicitly provided + default: 'World' + # Input has to be provided for the workflow to run + required: true + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "greet" + greet: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Runs a single command using the runners shell + - name: Send greeting + run: echo "Hello ${{ github.event.inputs.name }}" diff --git a/.github/workflows/status.yml b/.github/workflows/status.yml new file mode 100644 index 00000000..9dd6986c --- /dev/null +++ b/.github/workflows/status.yml @@ -0,0 +1,26 @@ +name: PR status checks +on: + pull_request: + types: [assigned, opened, synchronize, reopened] +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [16.x] + steps: + - uses: actions/checkout@v1 + with: + # Checkout the head ref instead of the PR branch that github creates. + ref: ${{ github.head_ref }} + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Install and build + run: | + npm install + - name: Run status checks + run: node ./status-checks + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 00000000..31354ec1 --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000..dc49f0ab --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npm run check-husky diff --git a/contracts/mainnet/connectors/aave/v2-amm/main.sol b/contracts/mainnet/connectors/aave/v2-amm/main.sol index 5f89d04a..55090a97 100644 --- a/contracts/mainnet/connectors/aave/v2-amm/main.sol +++ b/contracts/mainnet/connectors/aave/v2-amm/main.sol @@ -9,6 +9,7 @@ import { AaveInterface } from "./interface.sol"; abstract contract AaveResolver is Events, Helpers { /** * @dev Deposit ETH/ERC20_Token. + * @notice Deposit a token to Aave v2 AMM for lending / collaterization. * @param token token address to deposit.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to deposit. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -52,6 +53,7 @@ abstract contract AaveResolver is Events, Helpers { /** * @dev Withdraw ETH/ERC20_Token. + * @notice Withdraw deposited token from Aave v2 AMM * @param token token address to withdraw.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to withdraw. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -87,6 +89,7 @@ abstract contract AaveResolver is Events, Helpers { /** * @dev Borrow ETH/ERC20_Token. + * @notice Borrow a token using Aave v2 AMM * @param token token address to borrow.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to borrow. * @param rateMode type of borrow debt.(For Stable: 1, Variable: 2) @@ -118,6 +121,7 @@ abstract contract AaveResolver is Events, Helpers { /** * @dev Payback borrowed ETH/ERC20_Token. + * @notice Payback debt owed on Aave v2 AMM. * @param token token address to payback.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to payback. * @param rateMode type of borrow debt.(For Stable: 1, Variable: 2) @@ -156,6 +160,7 @@ abstract contract AaveResolver is Events, Helpers { /** * @dev Enable collateral + * @notice Enable an array of tokens as collateral * @param tokens Array of tokens to enable collateral */ function enableCollateral( diff --git a/contracts/mainnet/connectors/chi/main.sol b/contracts/mainnet/connectors/chi/main.sol index b808f9c1..d707019a 100644 --- a/contracts/mainnet/connectors/chi/main.sol +++ b/contracts/mainnet/connectors/chi/main.sol @@ -5,7 +5,8 @@ import { Events } from "./events.sol"; abstract contract ChiResolver is Events, Helpers { /** - * @dev Mint CHI token. + * @dev Mint token. + * @notice Mint CHI token. * @param amt token amount to mint. */ function mint(uint amt) public payable returns (string memory _eventName, bytes memory _eventParam) { @@ -18,7 +19,8 @@ abstract contract ChiResolver is Events, Helpers { } /** - * @dev burn CHI token. + * @dev Burn token. + * @notice burns CHI token. * @param amt token amount to burn. */ function burn(uint amt) public payable returns (string memory _eventName, bytes memory _eventParam) { diff --git a/contracts/mainnet/connectors/dydx/main.sol b/contracts/mainnet/connectors/dydx/main.sol index 5cfa9aeb..a113c2ea 100644 --- a/contracts/mainnet/connectors/dydx/main.sol +++ b/contracts/mainnet/connectors/dydx/main.sol @@ -9,6 +9,7 @@ abstract contract DyDxResolver is Events, Helpers { /** * @dev Deposit ETH/ERC20_Token. + * @notice Deposit a token to dYdX for lending / collaterization. * @param token token address to deposit.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to deposit. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -46,6 +47,7 @@ abstract contract DyDxResolver is Events, Helpers { /** * @dev Withdraw ETH/ERC20_Token. + * @notice Withdraw deposited token from dYdX. * @param token token address to withdraw.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to withdraw. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -82,6 +84,7 @@ abstract contract DyDxResolver is Events, Helpers { /** * @dev Borrow ETH/ERC20_Token. + * @notice Borrow a token using dYdX * @param token token address to borrow.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to borrow. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -115,6 +118,7 @@ abstract contract DyDxResolver is Events, Helpers { /** * @dev Payback borrowed ETH/ERC20_Token. + * @notice Payback debt owed. * @param token token address to payback.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to payback. * @param getId Get token amount at this ID from `InstaMemory` Contract. diff --git a/contracts/mainnet/connectors/fee/main.sol b/contracts/mainnet/connectors/fee/main.sol index e08a1bdf..2c4e89e8 100644 --- a/contracts/mainnet/connectors/fee/main.sol +++ b/contracts/mainnet/connectors/fee/main.sol @@ -6,6 +6,7 @@ import { Basic } from "../../common/basic.sol"; abstract contract FeeResolver is DSMath, Basic { /** * @dev Calculate fee + * @notice Calculates fee on a given amount * @param amount token amount to caculate fee. * @param fee fee percentage. Eg: 1% => 1e17, 100% => 1e18. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -31,6 +32,7 @@ abstract contract FeeResolver is DSMath, Basic { /** * @dev Calculate amount minus fee + * @notice Calculates amount minus fee * @param amount token amount to caculate fee. * @param fee fee percentage. Eg: 1% => 1e17, 100% => 1e18. * @param getId Get token amount at this ID from `InstaMemory` Contract. diff --git a/contracts/mainnet/connectors/gelato/main.sol b/contracts/mainnet/connectors/gelato/main.sol index aebc48a2..b852f1d5 100644 --- a/contracts/mainnet/connectors/gelato/main.sol +++ b/contracts/mainnet/connectors/gelato/main.sol @@ -13,12 +13,15 @@ abstract contract GelatoResolver is DSMath, Basic, Events { // ===== Gelato ENTRY APIs ====== /** - * @dev Enables first time users to pre-fund eth, whitelist an executor & register the + * @dev Enables to use Gelato + * @notice Enables first time users to pre-fund eth, whitelist an executor & register the * ProviderModuleDSA.sol to be able to use Gelato * @param _executor address of single execot node or gelato'S decentralized execution market * @param _taskSpecs enables external providers to whitelist TaskSpecs on gelato * @param _modules address of ProviderModuleDSA * @param _ethToDeposit amount of eth to deposit on Gelato, only for self-providers + * @param _getId get token amount at this IDs from `InstaMemory` Contract. + * @param _setId set token amount at this IDs in `InstaMemory` Contract. */ function multiProvide( address _executor, @@ -44,7 +47,8 @@ abstract contract GelatoResolver is DSMath, Basic, Events { } /** - * @dev Submits a single, one-time task to Gelato + * @dev Submit task + * @notice Submits a single, one-time task to Gelato * @param _provider Consists of proxy module address (DSA) and provider address () * who will pay for the transaction execution * @param _task Task specifying the condition and the action connectors @@ -62,7 +66,8 @@ abstract contract GelatoResolver is DSMath, Basic, Events { } /** - * @dev Submits single or mulitple Task Sequences to Gelato + * @dev Submit Task Sequences + * @notice Submits single or mulitple Task Sequences to Gelato * @param _provider Consists of proxy module address (DSA) and provider address () * who will pay for the transaction execution * @param _tasks A sequence of Tasks, can be a single or multiples @@ -87,7 +92,8 @@ abstract contract GelatoResolver is DSMath, Basic, Events { } /** - * @dev Submits single or mulitple Task Chains to Gelato + * @dev Submit Task Chains + * @notice Submits single or mulitple Task Chains to Gelato * @param _provider Consists of proxy module address (DSA) and provider address () * who will pay for the transaction execution * @param _tasks A sequence of Tasks, can be a single or multiples @@ -115,11 +121,15 @@ abstract contract GelatoResolver is DSMath, Basic, Events { // ===== Gelato EXIT APIs ====== /** - * @dev Withdraws funds from Gelato, de-whitelists TaskSpecs and Provider Modules + * @dev Withdraws ETH, de-whitelists Tasks and Modules + * in one tx + * @notice Withdraws funds from Gelato, de-whitelists TaskSpecs and Provider Modules * in one tx * @param _withdrawAmount Amount of ETH to withdraw from Gelato * @param _taskSpecs List of Task Specs to de-whitelist, default empty [] * @param _modules List of Provider Modules to de-whitelist, default empty [] + * @param _getId get token amount at this IDs from `InstaMemory` Contract. + * @param _setId set token amount at this IDs in `InstaMemory` Contract. */ function multiUnprovide( uint256 _withdrawAmount, @@ -146,7 +156,8 @@ abstract contract GelatoResolver is DSMath, Basic, Events { } /** - * @dev Cancels outstanding Tasks + * @dev Cancel Tasks + * @notice Cancels outstanding Tasks on Gelato * @param _taskReceipts List of Task Receipts to cancel */ function multiCancelTasks(TaskReceipt[] calldata _taskReceipts) diff --git a/contracts/mainnet/connectors/instapool/main.sol b/contracts/mainnet/connectors/instapool/main.sol index 9f0504bc..96f75ce8 100644 --- a/contracts/mainnet/connectors/instapool/main.sol +++ b/contracts/mainnet/connectors/instapool/main.sol @@ -9,6 +9,7 @@ import { Events } from "./events.sol"; abstract contract LiquidityManage is Helpers, Events { /** * @dev Deposit Liquidity in InstaPool. + * @notice Deposit Liquidity in InstaPool. * @param token token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -40,7 +41,8 @@ abstract contract LiquidityManage is Helpers, Events { } /** - * @dev Withdraw Liquidity in InstaPool. + * @dev Withdraw Liquidity from InstaPool. + * @notice Withdraw Liquidity from InstaPool. * @param token token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -64,8 +66,10 @@ abstract contract LiquidityManage is Helpers, Events { abstract contract LiquidityAccessHelper is LiquidityManage { /** - * @dev Add Fee Amount to borrowed flashloan/ - * @param amt Get token amount at this ID from `InstaMemory` Contract. + * @dev Add Fee Amount to borrowed flashloan. + * @notice Add Fee Amount to borrowed flashloan. + * @param token token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param amt token amount. * @param getId Get token amount at this ID from `InstaMemory` Contract. * @param setId Set token amount at this ID in `InstaMemory` Contract. */ @@ -81,6 +85,7 @@ abstract contract LiquidityAccessHelper is LiquidityManage { contract LiquidityAccess is LiquidityAccessHelper { /** * @dev Access Token Liquidity from InstaPool. + * @notice Take flashloan from InstaPool. * @param token token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -109,6 +114,7 @@ contract LiquidityAccess is LiquidityAccessHelper { /** * @dev Return Token Liquidity from InstaPool. + * @notice Payback borrowed flashloan to InstaPool. * @param token token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param getId Get token amount at this ID from `InstaMemory` Contract. * @param setId Set token amount at this ID in `InstaMemory` Contract. @@ -139,6 +145,7 @@ contract LiquidityAccess is LiquidityAccessHelper { /** * @dev Return Token Liquidity from InstaPool and Transfer 20% of Collected Fee to `origin`. + * @notice Payback borrowed flashloan to InstaPool and Transfer 20% of Collected Fee to `origin`. * @param origin origin address to transfer 20% of the collected fee. * @param token token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -177,6 +184,7 @@ contract LiquidityAccess is LiquidityAccessHelper { contract LiquidityAccessMulti is LiquidityAccess { /** * @dev Access Multiple Token liquidity from InstaPool. + * @notice Take multiple tokens flashloan from Instapool. * @param tokens Array of token addresses.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amts Array of token amount. * @param getId get token amounts at this IDs from `InstaMemory` Contract. @@ -206,6 +214,7 @@ contract LiquidityAccessMulti is LiquidityAccess { /** * @dev Return Multiple token liquidity from InstaPool. + * @notice Payback borrowed multiple tokens flashloan to Instapool. * @param tokens Array of token addresses.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param getId get token amounts at this IDs from `InstaMemory` Contract. * @param setId set token amounts at this IDs in `InstaMemory` Contract. diff --git a/contracts/mainnet/connectors/kyber/main.sol b/contracts/mainnet/connectors/kyber/main.sol index eb90ac80..c1ef72cd 100644 --- a/contracts/mainnet/connectors/kyber/main.sol +++ b/contracts/mainnet/connectors/kyber/main.sol @@ -7,6 +7,7 @@ import { Events } from "./events.sol"; abstract contract KyberResolver is Helpers, Events { /** * @dev Sell ETH/ERC20_Token. + * @notice Sell tokens using Kyber. * @param buyAddr buying token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param sellAddr selling token amount.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param sellAmt selling token amount. diff --git a/contracts/mainnet/connectors/oasis/main.sol b/contracts/mainnet/connectors/oasis/main.sol index 311bdc08..5742b3a5 100644 --- a/contracts/mainnet/connectors/oasis/main.sol +++ b/contracts/mainnet/connectors/oasis/main.sol @@ -14,6 +14,7 @@ contract OasisResolver is DSMath, Basic, Events { /** * @dev Buy ETH/ERC20_Token. + * @notice Buy tokens using Oasis. * @param buyAddr buying token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param sellAddr selling token amount.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param buyAmt buying token amount. @@ -64,6 +65,7 @@ contract OasisResolver is DSMath, Basic, Events { /** * @dev Sell ETH/ERC20_Token. + * @notice Sell tokens using Oasis. * @param buyAddr buying token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param sellAddr selling token amount.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param sellAmt selling token amount. diff --git a/contracts/mainnet/connectors/reflexer/main.sol b/contracts/mainnet/connectors/reflexer/main.sol index f00dcb1a..bba0ea5a 100644 --- a/contracts/mainnet/connectors/reflexer/main.sol +++ b/contracts/mainnet/connectors/reflexer/main.sol @@ -8,6 +8,7 @@ import { SafeEngineLike, TokenJoinInterface } from "./interface.sol"; abstract contract GebResolver is Helpers, Events { /** * @dev Open Safe + * @notice Open a Reflexer Safe. * @param colType Type of Collateral.(eg: 'ETH-A') */ function open(string calldata colType) external payable returns (string memory _eventName, bytes memory _eventParam) { @@ -21,6 +22,7 @@ abstract contract GebResolver is Helpers, Events { /** * @dev Close Safe + * @notice Close a Reflexer Safe. * @param safe Safe ID to close. */ function close(uint256 safe) external payable returns (string memory _eventName, bytes memory _eventParam) { @@ -39,6 +41,7 @@ abstract contract GebResolver is Helpers, Events { /** * @dev Deposit ETH/ERC20_Token Collateral. + * @notice Deposit collateral to a Reflexer safe * @param safe Safe ID. * @param amt token amount to deposit. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -85,6 +88,7 @@ abstract contract GebResolver is Helpers, Events { /** * @dev Withdraw ETH/ERC20_Token Collateral. + * @notice Withdraw collateral from a Reflexer Safe * @param safe Safe ID. * @param amt token amount to withdraw. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -140,6 +144,7 @@ abstract contract GebResolver is Helpers, Events { /** * @dev Borrow Coin. + * @notice Borrow Coin using a Reflexer safe * @param safe Safe ID. * @param amt token amount to borrow. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -188,6 +193,7 @@ abstract contract GebResolver is Helpers, Events { /** * @dev Payback borrowed Coin. + * @notice Payback Coin debt owed by a Reflexer safe * @param safe Safe ID. * @param amt token amount to payback. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -233,6 +239,7 @@ abstract contract GebResolver is Helpers, Events { /** * @dev Withdraw leftover ETH/ERC20_Token after Liquidation. + * @notice Withdraw leftover collateral after Liquidation. * @param safe Safe ID. * @param amt token amount to Withdraw. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -283,8 +290,10 @@ abstract contract GebResolver is Helpers, Events { SafeEngineLike safeEngineContract; TokenInterface tokenContract; } + /** * @dev Deposit ETH/ERC20_Token Collateral and Borrow Coin. + * @notice Deposit collateral and borrow Coin. * @param safe Safe ID. * @param depositAmt token deposit amount to Withdraw. * @param borrowAmt token borrow amount to Withdraw. @@ -365,6 +374,7 @@ abstract contract GebResolver is Helpers, Events { /** * @dev Exit Coin from handler. + * @notice Exit Coin from handler. * @param safe Safe ID. * @param amt token amount to exit. * @param getId Get token amount at this ID from `InstaMemory` Contract. diff --git a/contracts/polygon/connectors/paraswap/main.sol b/contracts/polygon/connectors/paraswap/main.sol index ed8a4b34..72eb0285 100644 --- a/contracts/polygon/connectors/paraswap/main.sol +++ b/contracts/polygon/connectors/paraswap/main.sol @@ -5,6 +5,16 @@ import { Stores } from "../../common/stores.sol"; import { Helpers } from "./helpers.sol"; abstract contract ParaswapResolver is Helpers { + /** + * @dev Sell ETH/ERC20_Token using ParaSwap. + * @notice Swap tokens from exchanges like kyber, 0x etc, with calculation done off-chain. + * @param buyAddr The address of the token to buy.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAddr The address of the token to sell.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAmt The amount of the token to sell. + * @param unitAmt The amount of buyAmt/sellAmt with slippage. + * @param callData Data from 1inch API. + * @param setId ID stores the amount of token brought. + */ function swap( address buyAddr, address sellAddr, diff --git a/package.json b/package.json index 4d64b041..b75eeb9c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "scripts": { "test": "hardhat test", "coverage": "./node_modules/.bin/solidity-coverage", - "build-contracts": "sol-merger \"./contracts/connectors/mock.sol\" ./contracts/build" + "check-husky": "node status-checks/huskyCheck.js", + "build-contracts": "sol-merger \"./contracts/connectors/mock.sol\" ./contracts/build", + "prepare": "husky install" }, "repository": { "type": "git", @@ -39,6 +41,7 @@ "@tenderly/hardhat-tenderly": "^1.0.6", "ethers": "^5.0.32", "hardhat": "^2.0.8", + "husky": "^6.0.0", "sol-merger": "^2.0.1", "solidity-coverage": "0.5.11", "web3": "^1.3.4" diff --git a/status-checks/check.js b/status-checks/check.js new file mode 100644 index 00000000..3eeb6184 --- /dev/null +++ b/status-checks/check.js @@ -0,0 +1,317 @@ +const fs = require('fs') +const path = require('path') + +const forbiddenStrings = ['selfdestruct'] + +const getConnectorsList = async () => { + try { + const connectors = [] + const connectorsRootsDirs = ['mainnet', 'polygon'] + for (let index = 0; index < connectorsRootsDirs.length; index++) { + const root = `contracts/${connectorsRootsDirs[index]}/connectors` + const dirs = [root] + while (dirs.length) { + const currentDir = dirs.pop() + const subs = fs.readdirSync(currentDir, { withFileTypes: true }) + for (let index = 0; index < subs.length; index++) { + const sub = subs[index] + if (sub.isFile() && sub.name === 'main.sol') { + connectors.push(currentDir) + } else if (sub.isDirectory()) { + dirs.push(`${currentDir}/${sub.name}`) + } + } + } + } + return connectors.map(dir => ({ path: dir })) + } catch (error) { + return Promise.reject(error) + } +} + +const checkCodeForbidden = async (code, codePath) => { + try { + const forbidden = [] + for (let i1 = 0; i1 < forbiddenStrings.length; i1++) { + const forbiddenStr = forbiddenStrings[i1] + const strs = code.split('\n') + for (let i2 = 0; i2 < strs.length; i2++) { + if (strs[i2].includes(forbiddenStr)) { + forbidden.push(`found '${forbiddenStr}' in ${codePath}:${i2 + 1}`) + } + } + } + return forbidden + } catch (error) { + return Promise.reject(error) + } +} + +const checkForbidden = async (parentPath, codePath = './main.sol') => { + try { + if (codePath.startsWith('@')) { + codePath = path.resolve('node_modules', `./${codePath}`) + } else { + codePath = path.resolve(parentPath, codePath) + } + const code = fs.readFileSync(codePath, { encoding: 'utf8' }) + const forbidden = await checkCodeForbidden(code, codePath) + if (code.includes('import')) { + const importsPathes = code + .split('\n') + .filter(str => str.includes('import') && str.includes('from') && str.includes('.sol')) + .map(str => str.split('from')[1].replace(/["; ]/gi, '')) + for (let index = 0; index < importsPathes.length; index++) { + const forbiddenErrors = await checkForbidden( + path.parse(codePath).dir, + importsPathes[index] + ) + forbidden.push(...forbiddenErrors) + } + } + return codePath.endsWith('main.sol') ? { forbiddenErrors: forbidden, code } : forbidden + } catch (error) { + return Promise.reject(error) + } +} + +const checkEvents = async (connector) => { + try { + const errors = [] + const warnings = [] + const eventsPath = `${connector.path}/events.sol` + const mainPath = `${connector.path}/main.sol` + if (connector.events.length) { + const eventNames = [] + for (let i1 = 0; i1 < connector.mainEvents.length; i1++) { + const mainEvent = connector.mainEvents[i1] + const name = mainEvent.split('(')[0] + eventNames.push(name) + const event = connector.events.find(e => e.split('(')[0].split(' ')[1] === name) + if (event) { + const mainEventArgs = mainEvent.split('(')[1].split(')')[0].split(',').map(a => a.trim()) + const eventArgs = event.split('(')[1].split(')')[0].split(',').map(a => a.trim()) + if (mainEventArgs.length !== eventArgs.length) { + errors.push(`arguments amount don't match for ${name} at ${mainPath}:${connector.mainEventsLines[i1]}`) + continue + } + for (let i2 = 0; i2 < mainEventArgs.length; i2++) { + if (!mainEventArgs[i2].startsWith(eventArgs[i2].split(' ')[0])) { + errors.push(`invalid argument #${i2 + 1} for ${name} at ${mainPath}:${connector.mainEventsLines[i1]}`) + } + } + } else { + errors.push(`event ${name} missing at ${eventsPath}`) + } + } + if (connector.mainEvents.length < connector.events.length) { + const deprecatedEvents = connector.events.filter(e => { + let used = false + for (let index = 0; index < eventNames.length; index++) { + if (e.split('(')[0].split(' ')[1] === eventNames[index]) used = true + } + return !used + }) + warnings.push(`${deprecatedEvents.map(e => e.split('(')[0].split(' ')[1]).join(', ')} event(s) not used at ${connector.path}/main.sol`) + } + } else { + warnings.push(`missing events file for ${connector.path}/main.sol`) + } + return { eventsErrors: errors, eventsWarnings: warnings } + } catch (error) { + return Promise.reject(error) + } +} + +const getCommments = async (strs) => { + try { + const comments = [] + let type + for (let index = strs.length - 1; index >= 0; index--) { + const str = strs[index] + if (!type) { + if (str.trim().startsWith('//')) { + type = 'single' + } else if (str.trim().startsWith('*/')) { + type = 'multiple' + } + } + if (type === 'single' && str.trim().startsWith('//')) { + comments.push(str.replace(/[/]/gi, '').trim()) + } else if (type === 'multiple' && !str.trim().startsWith('/**') && !str.trim().startsWith('*/')) { + comments.push(str.replace(/[*]/gi, '').trim()) + } else if (type === 'single' && !str.trim().startsWith('//')) { + break + } else if (type === 'multiple' && str.trim().startsWith('/**')) { + break + } + } + return comments + } catch (error) { + return Promise.reject(error) + } +} + +const parseCode = async (connector) => { + try { + const strs = connector.code.split('\n') + const events = [] + const eventsFirstLines = [] + let func = [] + let funcs = [] + let event = [] + let mainEvents = [] + let firstLine + let mainEventsLines = [] + for (let index = 0; index < strs.length; index++) { + const str = strs[index] + if (str.includes('function') && !str.trim().startsWith('//')) { + func = [str] + firstLine = index + 1 + } else if (func.length && !str.trim().startsWith('//')) { + func.push(str) + } + if (func.length && str.startsWith(`${func[0].split('function')[0]}}`)) { + funcs.push({ + raw: func.map(str => str.trim()).join(' '), + comments: await getCommments(strs.slice(0, firstLine)), + firstLine + }) + func = [] + } + } + funcs = funcs + .filter(({ raw }) => { + if ((raw.includes('external') || raw.includes('public')) && + raw.includes('returns')) { + const returns = raw.split('returns')[1].split('(')[1].split(')')[0] + return returns.includes('string') && returns.includes('bytes') + } + return false + }) + .map(f => { + const args = f.raw.split('(')[1].split(')')[0].split(',') + .map(arg => arg.trim()) + .filter(arg => arg !== '') + const name = f.raw.split('(')[0].split('function')[1].trim() + return { + ...f, + args, + name + } + }) + const eventsPath = `${connector.path}/events.sol` + if (fs.existsSync(eventsPath)) { + mainEvents = funcs + .map(({ raw }) => raw.split('_eventName')[2].trim().split('"')[1]) + .filter(raw => !!raw) + mainEventsLines = mainEvents.map(me => strs.findIndex(str => str.includes(me)) + 1) + const eventsCode = fs.readFileSync(eventsPath, { encoding: 'utf8' }) + const eventsStrs = eventsCode.split('\n') + for (let index = 0; index < eventsStrs.length; index++) { + const str = eventsStrs[index] + if (str.includes('event')) { + event = [str] + firstLine = index + 1 + } else if (event.length && !str.trim().startsWith('//')) { + event.push(str) + } + if (event.length && str.includes(')')) { + events.push(event.map(str => str.trim()).join(' ')) + eventsFirstLines.push(firstLine) + event = [] + } + } + } + return { + ...connector, + events, + eventsFirstLines, + mainEvents, + mainEventsLines, + funcs + } + } catch (error) { + return Promise.reject(error) + } +} + +const checkComments = async (connector) => { + try { + const errors = [] + for (let i1 = 0; i1 < connector.funcs.length; i1++) { + const func = connector.funcs[i1] + for (let i2 = 0; i2 < func.args.length; i2++) { + const argName = func.args[i2].split(' ').pop() + if (!func.comments.some( + comment => comment.startsWith('@param') && comment.split(' ')[1] === argName + )) { + errors.push(`argument ${argName} has no @param for function ${func.name} at ${connector.path}/main.sol:${func.firstLine}`) + } + } + const reqs = ['@dev', '@notice'] + for (let i3 = 0; i3 < reqs.length; i3++) { + if (!func.comments.some(comment => comment.startsWith(reqs[i3]))) { + errors.push(`no ${reqs[i3]} for function ${func.name} at ${connector.path}/main.sol:${func.firstLine}`) + } + } + } + return errors + } catch (error) { + return Promise.reject(error) + } +} + +const checkName = async (connector) => { + try { + const strs = connector.code.split('\n') + let haveName = false + for (let index = strs.length - 1; index > 0; index--) { + const str = strs[index] + if (str.includes('string') && str.includes('public') && str.includes('name = ')) { + haveName = true + } + } + return haveName ? [] : [`name variable missing in ${connector.path}/main.sol`] + } catch (error) { + return Promise.reject(error) + } +} + +async function checkMain () { + const errors = [] + try { + const warnings = [] + const connectors = await getConnectorsList() + for (let index = 0; index < connectors.length; index++) { + const { forbiddenErrors, code } = await checkForbidden(connectors[index].path) + connectors[index].code = code + connectors[index] = await parseCode(connectors[index]) + const { eventsErrors, eventsWarnings } = await checkEvents(connectors[index]) + const commentsErrors = await checkComments(connectors[index]) + const nameErrors = await checkName(connectors[index]) + + errors.push(...forbiddenErrors) + errors.push(...eventsErrors) + errors.push(...commentsErrors) + errors.push(...nameErrors) + warnings.push(...eventsWarnings) + } + if (errors.length) { + console.log('\x1b[31m%s\x1b[0m', `Total errors: ${errors.length}`) + console.log('\x1b[31m%s\x1b[0m', errors.join('\n')) + } else { + console.log('\x1b[32m%s\x1b[0m', 'No Errors Found') + } + if (warnings.length) { + console.log('\x1b[33m%s\x1b[0m', `Total warnings: ${warnings.length}`) + console.log('\x1b[33m%s\x1b[0m', warnings.join('\n')) + } else { + console.log('\x1b[32m%s\x1b[0m', 'No Warnings Found') + } + } catch (error) { + console.error('check execution error:', error) + } + if (errors.length) throw new Error(errors.join('\n')) +} +module.exports = checkMain; \ No newline at end of file diff --git a/status-checks/checks.js b/status-checks/checks.js new file mode 100644 index 00000000..9a52768b --- /dev/null +++ b/status-checks/checks.js @@ -0,0 +1,6 @@ +const checkMain = require('./check'); + +module.exports = [{ + name: 'Solidity check', + callback: checkMain, +}]; diff --git a/status-checks/huskyCheck.js b/status-checks/huskyCheck.js new file mode 100644 index 00000000..e1e5f4fc --- /dev/null +++ b/status-checks/huskyCheck.js @@ -0,0 +1,9 @@ +const checkMain = require('./check'); + +(async function runHusky () { + try { + await checkMain() + } catch (error) { + process.exit(1) + } +})() \ No newline at end of file diff --git a/status-checks/index.js b/status-checks/index.js new file mode 100644 index 00000000..4dbd9aea --- /dev/null +++ b/status-checks/index.js @@ -0,0 +1,54 @@ +const cp = require('child_process'); +const fetch = require('node-fetch'); + +const checks = require('./checks'); + +const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/'); + +function getCurrentCommitSha() { + return cp + .execSync(`git rev-parse HEAD`) + .toString() + .trim(); +} +// The SHA provied by GITHUB_SHA is the merge (PR) commit. +// We need to get the current commit sha ourself. +const sha = getCurrentCommitSha(); + +async function setStatus(context, state, description) { + return fetch(`https://api.github.com/repos/${owner}/${repo}/statuses/${sha}`, { + method: 'POST', + body: JSON.stringify({ + state, + description, + context, + }), + headers: { + Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, + 'Content-Type': 'application/json', + }, + }); +} + +(async () => { + console.log(`Starting status checks for commit ${sha}`); + + // Run in parallel + await Promise.all( + checks.map(async check => { + const { name, callback } = check; + + await setStatus(name, 'pending', 'Running check..'); + + try { + const response = await callback(); + await setStatus(name, 'success', response); + } catch (err) { + const message = err ? err.message : 'Something went wrong'; + await setStatus(name, 'failure', message); + } + }), + ); + + console.log('Finished status checks'); +})();