Checker for solidity code (#32)

* raw logic

* rename and output update

* checks update

* name & comments checks update

* Updated comments for reflexer

* Updated comments

* colors and line numbers

* commit test

* commit test 2

* commit test 3

* commit test 4

* commit test 5

* commit test 6

* commit test 7

* reset branch

* commit test 1

* commit test 1

* test commit 1

* test commit

* test

* test

* test workflow

Co-authored-by: Thrilok Kumar <thrilok2000@gmail.com>
This commit is contained in:
Ardhead 2021-05-07 19:36:52 +03:00 committed by GitHub
parent 3f715e90cf
commit 0ef4667258
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 518 additions and 12 deletions

30
.github/workflows/manual.yml vendored Normal file
View File

@ -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 }}"

26
.github/workflows/status.yml vendored Normal file
View File

@ -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 }}

1
.husky/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_

4
.husky/pre-commit Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run check-husky

View File

@ -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(

View File

@ -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) {

View File

@ -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.

View File

@ -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.

View File

@ -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)

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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,

View File

@ -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"

317
status-checks/check.js Normal file
View File

@ -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;

6
status-checks/checks.js Normal file
View File

@ -0,0 +1,6 @@
const checkMain = require('./check');
module.exports = [{
name: 'Solidity check',
callback: checkMain,
}];

View File

@ -0,0 +1,9 @@
const checkMain = require('./check');
(async function runHusky () {
try {
await checkMain()
} catch (error) {
process.exit(1)
}
})()

54
status-checks/index.js Normal file
View File

@ -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');
})();