diff --git a/.commitlintrc.js b/.commitlintrc.js new file mode 100644 index 0000000..69b4242 --- /dev/null +++ b/.commitlintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ["@commitlint/config-conventional"], +}; diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6fd96c1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# EditorConfig http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# All files +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.sol] +indent_size = 4 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..367c6c9 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +INFURA_API_KEY=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +MNEMONIC=here is where your twelve words mnemonic should be put my friend diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..6b1e7bb --- /dev/null +++ b/.eslintignore @@ -0,0 +1,13 @@ +# folders +artifacts/ +build/ +cache/ +coverage/ +dist/ +lib/ +node_modules/ +typechain/ + +# files +.solcover.js +coverage.json diff --git a/.eslintrc.yaml b/.eslintrc.yaml new file mode 100644 index 0000000..4973e0e --- /dev/null +++ b/.eslintrc.yaml @@ -0,0 +1,21 @@ +extends: + - eslint:recommended + - plugin:@typescript-eslint/eslint-recommended + - plugin:@typescript-eslint/recommended + - prettier/@typescript-eslint +parser: "@typescript-eslint/parser" +parserOptions: + project: tsconfig.json +plugins: + - "@typescript-eslint" +root: true +rules: + "@typescript-eslint/no-floating-promises": + - error + - ignoreIIFE: true + ignoreVoid: true + "@typescript-eslint/no-inferrable-types": "off" + "@typescript-eslint/no-unused-vars": + - error + - argsIgnorePattern: _ + varsIgnorePattern: _ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7cc88f0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sol linguist-language=Solidity \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8aab48 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# folders +.coverage_artifacts/ +.coverage_cache/ +.coverage_contracts/ +artifacts/ +build/ +cache/ +coverage/ +dist/ +lib/ +node_modules/ +typechain/ + +# files +*.env +*.log +*.tsbuildinfo +coverage.json +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/.huskyrc b/.huskyrc new file mode 100644 index 0000000..2a4718c --- /dev/null +++ b/.huskyrc @@ -0,0 +1,5 @@ +{ + "hooks": { + "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" + } +} diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 0000000..ecb4455 --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,6 @@ +{ + "extension": ["ts"], + "recursive": "test", + "require": ["hardhat/register"], + "timeout": 20000 +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..ad45072 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,12 @@ +# folders +artifacts/ +build/ +cache/ +coverage/ +dist/ +lib/ +node_modules/ +typechain/ + +# files +coverage.json diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..987cc8e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,17 @@ +{ + "arrowParens": "avoid", + "bracketSpacing": true, + "endOfLine":"auto", + "printWidth": 120, + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "all", + "overrides": [ + { + "files": "*.sol", + "options": { + "tabWidth": 4 + } + } + ] +} diff --git a/.solcover.js b/.solcover.js new file mode 100644 index 0000000..a0380ce --- /dev/null +++ b/.solcover.js @@ -0,0 +1,23 @@ +const shell = require("shelljs"); + +// The environment variables are loaded in hardhat.config.ts +const mnemonic = process.env.MNEMONIC; +if (!mnemonic) { + throw new Error("Please set your MNEMONIC in a .env file"); +} + +module.exports = { + istanbulReporter: ["html"], + onCompileComplete: async function (_config) { + await run("typechain"); + }, + onIstanbulComplete: async function (_config) { + // We need to do this because solcover generates bespoke artifacts. + shell.rm("-rf", "./artifacts"); + shell.rm("-rf", "./typechain"); + }, + providerOptions: { + mnemonic, + }, + skipFiles: ["mocks", "test"], +}; diff --git a/.solhint.json b/.solhint.json new file mode 100644 index 0000000..630fcc5 --- /dev/null +++ b/.solhint.json @@ -0,0 +1,20 @@ +{ + "extends": "solhint:recommended", + "plugins": ["prettier"], + "rules": { + "code-complexity": ["error", 7], + "compiler-version": ["error", "^0.7.0"], + "const-name-snakecase": "off", + "constructor-syntax": "error", + "func-visibility": ["error", { "ignoreConstructors": true }], + "max-line-length": ["error", 120], + "not-rely-on-time": "off", + "prettier/prettier": [ + "error", + { + "endOfLine": "auto" + } + ], + "reason-string": ["warn", { "maxLength": 64 }] + } +} diff --git a/.solhintignore b/.solhintignore new file mode 100644 index 0000000..5daf5e1 --- /dev/null +++ b/.solhintignore @@ -0,0 +1,5 @@ +# folders +.yarn/ +build/ +dist/ +node_modules/ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..77ac2a0 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,7 @@ +# WTFPL + +by Paul Razvan Berg (@PaulRBerg) + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0db365d --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# Solidity Template + +My favourite setup for writing Solidity smart contracts. + +- [Hardhat](https://github.com/nomiclabs/hardhat): compile and run the smart contracts on a local development network +- [TypeChain](https://github.com/ethereum-ts/TypeChain): generate TypeScript types for smart contracts +- [Ethers](https://github.com/ethers-io/ethers.js/): renowned Ethereum library and wallet implementation +- [Waffle](https://github.com/EthWorks/Waffle): tooling for writing comprehensive smart contract tests +- [Solhint](https://github.com/protofire/solhint): linter +- [Solcover](https://github.com/sc-forks/solidity-coverage) code coverage +- [Prettier Plugin Solidity](https://github.com/prettier-solidity/prettier-plugin-solidity): code formatter + +This is a GitHub template, which means you can reuse it as many times as you want. You can do that by clicking the "Use this +template" button at the top of the page. + +## Usage + +### Pre Requisites + +Before running any command, make sure to install dependencies: + +```sh +$ yarn install +``` + +### Compile + +Compile the smart contracts with Hardhat: + +```sh +$ yarn compile +``` + +### TypeChain + +Compile the smart contracts and generate TypeChain artifacts: + +```sh +$ yarn build +``` + +### Lint Solidity + +Lint the Solidity code: + +```sh +$ yarn lint:sol +``` + +### Lint TypeScript + +Lint the TypeScript code: + +```sh +$ yarn lint:ts +``` + +### Test + +Run the Mocha tests: + +```sh +$ yarn test +``` + +### Coverage + +Generate the code coverage report: + +```sh +$ yarn coverage +``` + +### Clean + +Delete the smart contract artifacts, the coverage reports and the Hardhat cache: + +```sh +$ yarn clean +``` diff --git a/contracts/Greeter.sol b/contracts/Greeter.sol new file mode 100644 index 0000000..4fbc95e --- /dev/null +++ b/contracts/Greeter.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.7.0; + +import "hardhat/console.sol"; + +contract Greeter { + string public greeting; + + constructor(string memory _greeting) { + console.log("Deploying a Greeter with greeting:", _greeting); + greeting = _greeting; + } + + function greet() public view returns (string memory) { + return greeting; + } + + function setGreeting(string memory _greeting) public { + console.log("Changing greeting from '%s' to '%s'", greeting, _greeting); + greeting = _greeting; + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts new file mode 100644 index 0000000..7ab66c8 --- /dev/null +++ b/hardhat.config.ts @@ -0,0 +1,86 @@ +import { config as dotenvConfig } from "dotenv"; +import { resolve } from "path"; +dotenvConfig({ path: resolve(__dirname, "./.env") }); + +import { HardhatUserConfig } from "hardhat/config"; +import { NetworkUserConfig } from "hardhat/types"; +import "./tasks/accounts"; +import "./tasks/clean"; + +import "@nomiclabs/hardhat-waffle"; +import "hardhat-typechain"; +import "solidity-coverage"; + +const chainIds = { + ganache: 1337, + goerli: 5, + hardhat: 31337, + kovan: 42, + mainnet: 1, + rinkeby: 4, + ropsten: 3, +}; + +// Ensure that we have all the environment variables we need. +let mnemonic: string; +if (!process.env.MNEMONIC) { + throw new Error("Please set your MNEMONIC in a .env file"); +} else { + mnemonic = process.env.MNEMONIC; +} + +let infuraApiKey: string; +if (!process.env.INFURA_API_KEY) { + throw new Error("Please set your INFURA_API_KEY in a .env file"); +} else { + infuraApiKey = process.env.INFURA_API_KEY; +} + +function createTestnetConfig(network: keyof typeof chainIds): NetworkUserConfig { + const url: string = "https://" + network + ".infura.io/v3/" + infuraApiKey; + return { + accounts: { + count: 10, + initialIndex: 0, + mnemonic, + path: "m/44'/60'/0'/0", + }, + chainId: chainIds[network], + url, + }; +} + +const config: HardhatUserConfig = { + defaultNetwork: "hardhat", + networks: { + hardhat: { + chainId: chainIds.hardhat, + }, + goerli: createTestnetConfig("goerli"), + kovan: createTestnetConfig("kovan"), + rinkeby: createTestnetConfig("rinkeby"), + ropsten: createTestnetConfig("ropsten"), + }, + paths: { + artifacts: "./artifacts", + cache: "./cache", + sources: "./contracts", + tests: "./test", + }, + solidity: { + version: "0.7.4", + settings: { + // https://hardhat.org/hardhat-network/#solidity-optimizer-support + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + typechain: { + outDir: "typechain", + target: "ethers-v5", + }, +}; + +export default config; diff --git a/package.json b/package.json new file mode 100644 index 0000000..b0a3a68 --- /dev/null +++ b/package.json @@ -0,0 +1,75 @@ +{ + "name": "@paulrberg/solidity-template", + "description": "Setup for writing Solidity smart contracts", + "version": "1.0.0", + "author": { + "name": "Paul Razvan Berg", + "url": "https://paulrberg.com" + }, + "devDependencies": { + "@commitlint/cli": "^9.1.2", + "@commitlint/config-conventional": "^9.1.2", + "@ethersproject/abstract-signer": "^5.0.6", + "@ethersproject/bignumber": "^5.0.8", + "@nomiclabs/hardhat-ethers": "^2.0.0", + "@nomiclabs/hardhat-waffle": "^2.0.0", + "@typechain/ethers-v5": "^2.0.0", + "@types/chai": "^4.2.13", + "@types/fs-extra": "^9.0.1", + "@types/mocha": "^7.0.2", + "@types/node": "^14.11.8", + "@typescript-eslint/eslint-plugin": "^3.10.1", + "@typescript-eslint/parser": "^3.10.1", + "chai": "^4.2.0", + "commitizen": "^4.2.1", + "cz-conventional-changelog": "^3.3.0", + "dotenv": "^8.2.0", + "eslint": "^7.11.0", + "eslint-config-prettier": "^6.12.0", + "ethereum-waffle": "^3.2.0", + "ethers": "^5.0.17", + "fs-extra": "^9.0.1", + "hardhat": "^2.0.2", + "hardhat-typechain": "^0.3.3", + "husky": "^4.3.0", + "mocha": "^8.1.3", + "prettier": "^2.1.2", + "prettier-plugin-solidity": "^1.0.0-alpha.59", + "shelljs": "^0.8.4", + "solc": "0.7.4", + "solhint": "^3.2.1", + "solhint-plugin-prettier": "^0.0.5", + "solidity-coverage": "^0.7.12", + "ts-generator": "^0.1.1", + "ts-node": "^8.10.2", + "typechain": "^3.0.0", + "typescript": "^3.9.7" + }, + "files": [ + "/contracts" + ], + "keywords": [ + "blockchain", + "ethereum", + "hardhat", + "smart-contracts", + "solidity" + ], + "license": "WTFPL", + "publishConfig": { + "access": "public" + }, + "scripts": { + "clean": "hardhat clean", + "commit": "git-cz", + "compile": "hardhat compile", + "coverage": "hardhat coverage --solcoverjs ./.solcover.js --temp artifacts --testfiles \"./test/**/*.ts\"", + "lint": "yarn run lint:sol && yarn run lint:ts && yarn run prettier:list-different", + "lint:sol": "solhint --config ./.solhint.json --max-warnings 0 \"contracts/**/*.sol\"", + "lint:ts": "eslint --config ./.eslintrc.yaml --ignore-path ./.eslintignore --ext .js,.ts .", + "prettier": "prettier --config .prettierrc --write \"**/*.{js,json,md,sol,ts}\"", + "prettier:list-different": "prettier --config .prettierrc --list-different \"**/*.{js,json,md,sol,ts}\"", + "test": "hardhat test", + "typechain": "hardhat typechain" + } +} diff --git a/scripts/deploy.ts b/scripts/deploy.ts new file mode 100644 index 0000000..0781d0d --- /dev/null +++ b/scripts/deploy.ts @@ -0,0 +1,29 @@ +// We require the Hardhat Runtime Environment explicitly here. This is optional +// but useful for running the script in a standalone fashion through `node