From 052f3be32cc371dae3498fa72ddc343aeb21310d Mon Sep 17 00:00:00 2001 From: dappsar Date: Wed, 26 Jul 2023 19:00:32 -0300 Subject: [PATCH] first commit --- .editorconfig | 21 +++++ .eslintrc | 20 ++++ .gitattributes | 1 + .github/dependabot.yml | 6 ++ .github/workflows/doc/workflows.md | 17 ++++ .github/workflows/scripts/comments.js | 26 +++++ .github/workflows/slither.yml | 35 +++++++ .github/workflows/test.yaml | 23 +++++ .gitignore | 79 ++++++++++++++++ .prettierignore | 3 + .prettierrc | 22 +++++ .solcover.js | 13 +++ .solhint.json | 8 ++ .solhintignore | 6 ++ README.md | 85 ++++++++++++++++- contracts/GigacountsToken.sol | 34 +++++++ example_env | 5 + hardhat.config.ts | 50 ++++++++++ package.json | 59 ++++++++++++ scripts/deploy.ts | 36 +++++++ scripts/interact.ts | 23 +++++ slither.config.json | 5 + solhint.config.js | 17 ++++ test/GigacountsToken.test.ts | 131 ++++++++++++++++++++++++++ tsconfig.json | 13 +++ 25 files changed, 736 insertions(+), 2 deletions(-) create mode 100644 .editorconfig create mode 100644 .eslintrc create mode 100644 .gitattributes create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/doc/workflows.md create mode 100644 .github/workflows/scripts/comments.js create mode 100644 .github/workflows/slither.yml create mode 100644 .github/workflows/test.yaml create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 .solcover.js create mode 100644 .solhint.json create mode 100644 .solhintignore create mode 100644 contracts/GigacountsToken.sol create mode 100644 example_env create mode 100644 hardhat.config.ts create mode 100644 package.json create mode 100644 scripts/deploy.ts create mode 100644 scripts/interact.ts create mode 100644 slither.config.json create mode 100644 solhint.config.js create mode 100644 test/GigacountsToken.test.ts create mode 100644 tsconfig.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c6664ca --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = false +max_line_length = 120 + +[*.sol] +indent_size = 4 + +[*.js] +indent_size = 2 + +[*.{adoc,md}] +max_line_length = 0 \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..820acea --- /dev/null +++ b/.eslintrc @@ -0,0 +1,20 @@ +{ + "root": true, + "extends" : [ + "eslint:recommended", + "prettier" + ], + "env": { + "es2022": true, + "browser": true, + "node": true, + "mocha": true + }, + "globals" : { + "artifacts": "readonly", + "contract": "readonly", + "web3": "readonly", + "extendEnvironment": "readonly", + "expect": "readonly" + } +} \ No newline at end of file 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/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..423c6ce --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" \ No newline at end of file diff --git a/.github/workflows/doc/workflows.md b/.github/workflows/doc/workflows.md new file mode 100644 index 0000000..9b3a86a --- /dev/null +++ b/.github/workflows/doc/workflows.md @@ -0,0 +1,17 @@ +# Github Actions Workflows + + +## Action Workflow dependabot.yml + +Enabling [Dependabot version updates for actions](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot) allow us to get notified of new action releases. + + +## Action Workflow slither.yml + + +The action supports the Github Code Scanning integration, which will push Slither's alerts to the Security tab of the Github project (see [About code scanning](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/about-code-scanning)). This integration eases the triaging of findings and improves the continuous integration. + +We include `fail-on: none` on the Slither action to avoid failing the run if findings are found. Also, it creates/updates pull requests with the contents of Slither's Markdown report. + + +[More Info](https://github.com/marketplace/actions/slither-action#examples) \ No newline at end of file diff --git a/.github/workflows/scripts/comments.js b/.github/workflows/scripts/comments.js new file mode 100644 index 0000000..d709c31 --- /dev/null +++ b/.github/workflows/scripts/comments.js @@ -0,0 +1,26 @@ +module.exports = async ({ github, context, header, body }) => { + const comment = [header, body].join("\n"); + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.number, + }); + + const botComment = comments.find( + (comment) => + // github-actions bot user + comment.user.id === 41898282 && comment.body.startsWith(header) + ); + + const commentFn = botComment ? "updateComment" : "createComment"; + + await github.rest.issues[commentFn]({ + owner: context.repo.owner, + repo: context.repo.repo, + body: comment, + ...(botComment + ? { comment_id: botComment.id } + : { issue_number: context.payload.number }) + }); +}; \ No newline at end of file diff --git a/.github/workflows/slither.yml b/.github/workflows/slither.yml new file mode 100644 index 0000000..2b7cf12 --- /dev/null +++ b/.github/workflows/slither.yml @@ -0,0 +1,35 @@ +name: Slither Analysis +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + analyze: + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Run Slither + uses: crytic/slither-action@v0.3.0 + id: slither + with: + node-version: 16 + sarif: results.sarif + fail-on: none + slither-args: --checklist --markdown-root ${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/ + + - name: Create/update checklist as PR comment + uses: actions/github-script@v6 + if: github.event_name == 'pull_request' + with: + script: | + const script = require('.github/scripts/comment') + const header = '# Slither report' + const body = `${{ steps.slither.outputs.stdout }}` + await script({ github, context, header, body }) \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..589d9c5 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,23 @@ +name: unit-test +on: + push: + branches: + - main +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: 16 + - name: Install dependencies + run: npm install --no-warnings + - name: Compile contracts + run: npx hardhat compile + - name: Test deploy harhat + run: npx hardhat node & npx hardhat run --network hardhat scripts/deploy.ts + - name: Run tests + run: npx hardhat test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54a23da --- /dev/null +++ b/.gitignore @@ -0,0 +1,79 @@ +*.swp +*.swo + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed +allFiredEvents +scTopics + +# Coverage directory used by tools like istanbul +coverage +coverage.json +coverageEnv + +# node-waf configuration +.lock-wscript + +# Dependency directory +node_modules + +# Debug log from npm +npm-debug.log + +# local env variables +.env + +# truffle build directory +build/ + +# macOS +.DS_Store + +# truffle +.node-xmlhttprequest-* + +# IntelliJ IDE +.idea + +# docs artifacts +docs/modules/api + +# only used to package @openzeppelin/contracts +contracts/build/ +contracts/README.md + +# temporary artifact from solidity-coverage +allFiredEvents +.coverage_artifacts +.coverage_cache +.coverage_contracts + +# hardat-exposed +contracts-exposed + +# Hardhat +/cache +/artifacts +/generated +typechain +typechain-types +src/ + +# Foundry +/out + +# Certora +.certora* +.last_confs +certora_* +.zip-output-url.txt + +# git +yarn.lock +package-lock* diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..38039b9 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +.github +build +node_modules \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..c525fa4 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,22 @@ +{ + "printWidth": 120, + "singleQuote": true, + "trailingComma": "none", + "semi": false, + "arrowParens": "avoid", + "plugins": ["prettier-plugin-solidity"], + "overrides": [ + { + "files": "*.sol", + "options": { + "parser": "solidity-parse", + "printWidth": 80, + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": false, + "bracketSpacing": false + } + } + ] +} diff --git a/.solcover.js b/.solcover.js new file mode 100644 index 0000000..7739a77 --- /dev/null +++ b/.solcover.js @@ -0,0 +1,13 @@ +module.exports = { + norpc: true, + testCommand: 'npm test', + compileCommand: 'npm run compile', + skipFiles: ['mocks'], + providerOptions: { + default_balance_ether: '10000000000000000000000000' + }, + mocha: { + fgrep: '[skip-on-coverage]', + invert: true + } +} \ No newline at end of file diff --git a/.solhint.json b/.solhint.json new file mode 100644 index 0000000..fa87ce6 --- /dev/null +++ b/.solhint.json @@ -0,0 +1,8 @@ +{ + "extends": "solhint:default", + "plugins": [], + "rules": { + "avoid-suicide": "error", + "avoid-sha3": "warn" + } +} \ No newline at end of file diff --git a/.solhintignore b/.solhintignore new file mode 100644 index 0000000..1c50df7 --- /dev/null +++ b/.solhintignore @@ -0,0 +1,6 @@ +node_modules/ +test/ +scripts/ +tasks/ +cache/ +artifacts/ \ No newline at end of file diff --git a/README.md b/README.md index 562b799..28bdbb6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,83 @@ -# giga_gigacounts_blockchain -blockchain codebase for gigacounts +# Gigacounts blockchain + +## Requirements + +The App requires: + +- [Node.js](https://nodejs.org/) v16+ to run (^16.14.2). +- [Yarn.js](https://classic.yarnpkg.com/en/docs/install) v1+ to run (^1.22.19). +- [hardhat](https://hardhat.org/) + +- You could check the version of packages (node, yarn) with these commands: + +```sh +node -v +yarn -v +``` + +## Install the dependencies + +```sh +- yarn install # with yarn +- npm i OR npm i --legacy-peer-deps # with NPM +``` + +If you have some trouble with dependencies, try this: + +```sh +set http_proxy= +set https_proxy= +npm config rm https-proxy +npm config rm proxy +npm config set registry "https://registry.npmjs.org" +yarn cache clean +yarn config delete proxy +yarn --network-timeout 100000 +``` + +Create a .env file running the command in terminal + +```sh +touch .env +``` + +## Environment variables + +The environment variables below needs to be set in the .env file when the project is running locally. + +```sh +INFURA_ID={your infura project id} +ALCHEMY_ID={your infura api key} +PUBLIC_ADDRESS={your wallet address} +PRIVATE_KEY={your private key to deploy SCs} +DEPLOYED_GIGACOUNTS_CONTRACT_ADDRESS={the address where contract ERC20 is deploy, just to use in scripts/interact.ts} +``` + +> Note: You can find more info about the other required `.env` variables inside the `example_env` file. + + +## Commands + +```shell +# Run testnet +yarn hardhat node + +# Compile +npx hardhat compile + +# Test +npx hardhat test + +# Deploy to local +npx hardhat run scripts/deploy.ts --network localhost + +# Deploy to Mumnbai +npx hardhat run scripts/deploy.ts --network mumbai + +# Solidity Security and Style guides validations with solhint [https://protofire.github.io/solhint/] +npm install -g solhint +solhint 'contracts/**/*.sol' + +# Solidity Static Analysis [https://github.com/crytic/slither] +slither . +``` diff --git a/contracts/GigacountsToken.sol b/contracts/GigacountsToken.sol new file mode 100644 index 0000000..f2e03a1 --- /dev/null +++ b/contracts/GigacountsToken.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import "@openzeppelin/contracts/security/Pausable.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +/// @custom:security-contact giga@unicef.org +contract GigacountsToken is ERC20, ERC20Burnable, Pausable, Ownable { + constructor() ERC20("GigacountsToken", "GCTK") { + _mint(msg.sender, 100000000 * 10 ** decimals()); + } + + function pause() public onlyOwner { + _pause(); + } + + function unpause() public onlyOwner { + _unpause(); + } + + function mint(address to, uint256 amount) public onlyOwner { + _mint(to, amount); + } + + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal override whenNotPaused { + super._beforeTokenTransfer(from, to, amount); + } +} diff --git a/example_env b/example_env new file mode 100644 index 0000000..aa9616f --- /dev/null +++ b/example_env @@ -0,0 +1,5 @@ +INFURA_ID={your infura porject id} +ALCHEMY_ID={your infura api key} +PUBLIC_ADDRESS={your wallet address} +PRIVATE_KEY={your private key to deploy SCs} +DEPLOYED_GIGACOUNTS_CONTRACT_ADDRESS={the address where contract ERC20 is deploy, just to use in scripts/interact.ts} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts new file mode 100644 index 0000000..9c61ca7 --- /dev/null +++ b/hardhat.config.ts @@ -0,0 +1,50 @@ +import dotenv from "dotenv"; +import { HardhatUserConfig } from "hardhat/config"; +import "@nomicfoundation/hardhat-toolbox"; + +dotenv.config() + +const INFURA_API_KEY = process.env.INFURA_API_KEY || 'INFURA_API_KEY' +const ALCHEMY_API_KEY = process.env.ALCHEMY_API_KEY || 'ALCHEMY_API_KEY' +const PRIVATE_KEY = process.env.PRIVATE_KEY + +const config: HardhatUserConfig = { + defaultNetwork: 'hardhat', + networks: { + hardhat: { + }, + goerli: { + url: `https://goerli.infura.io/v3/${INFURA_API_KEY}`, + ...(PRIVATE_KEY ? { accounts: [`${PRIVATE_KEY}`] } : {}) + }, + mumbai: { + url: `https://polygon-mumbai.g.alchemy.com/v2/${ALCHEMY_API_KEY}`, + ...(PRIVATE_KEY ? { accounts: [`${PRIVATE_KEY}`] } : {}) + }, + sepolia: { + url: `https://sepolia.infura.io/v3/${INFURA_API_KEY}`, + ...(PRIVATE_KEY ? { accounts: [`${PRIVATE_KEY}`] } : {}) + } + }, + solidity: { + compilers: [ + { + version: '0.8.9', + settings: { + optimizer: { + enabled: true, + runs: 200 + } + } + } + ] + }, + paths: { + sources: './contracts', + tests: './test', + cache: './cache', + artifacts: './artifacts' + } +} + +export default config diff --git a/package.json b/package.json new file mode 100644 index 0000000..52f30a1 --- /dev/null +++ b/package.json @@ -0,0 +1,59 @@ +{ + "name": "gigacounts-blockchain", + "description": "Smart Contracts for Gigacounts application", + "version": "1.0.0", + "author": "Giga/UNICEF", + "license": "MIT", + "keywords": [ + "solidity", + "ethereum", + "smart", + "contracts", + "security", + "zeppelin", + "gigacounts", + "erc20" + ], + "files": [ + "/contracts/**/*.sol" + ], + "scripts": { + "compile": "hardhat compile", + "test": "hardhat test", + "deploy-local": "npx hardhat run scripts/deploy.ts --network localhost", + "deploy-mumbai": "npx hardhat run scripts/deploy.ts --network mumbai", + "lint": "npm run lint:js & npm run lint:sol", + "lint:fix": "npm run lint:js:fix & npm run lint:sol:fix", + "lint:js": "npx prettier --log-level warn --ignore-path .gitignore \"{contracts,test}/**/*.{js,ts}\" --check & npx eslint --ignore-path .gitignore .", + "lint:js:fix": "npx prettier --log-level warn --ignore-path .gitignore \"{contracts,test}/**/*.{js,ts}\" --write & npx eslint --ignore-path .gitignore . --fix", + "lint:sol": "npx prettier --log-level warn --ignore-path .gitignore \"{contracts,test}/**/*.sol\" --check & npx solhint \"{contracts,test}/**/*.sol\"", + "lint:sol:fix": "npx prettier --log-level warn --ignore-path .gitignore \"{contracts,test}/**/*.sol\" --write", + "slither": "npm run clean && slither ." + }, + "devDependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/providers": "^5.7.2", + "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", + "@nomicfoundation/hardhat-network-helpers": "^1.0.7", + "@nomicfoundation/hardhat-toolbox": "^1.0.2", + "@nomiclabs/hardhat-ethers": "^2.2.3", + "@nomiclabs/hardhat-etherscan": "^3.1.4", + "@openzeppelin/contracts": "^4.9.2", + "@openzeppelin/contracts-upgradeable": "^4.9.2", + "@types/node": "^20.4.2", + "chai": "^4.3.7", + "dotenv": "^10.0.0", + "eslint": "^8.30.0", + "eslint-config-prettier": "^8.5.0", + "ethereum-waffle": "^2.2.0", + "ethers": "^5.7.2", + "hardhat": "^2.12.5", + "hardhat-gas-reporter": "^1.0.2", + "hardhat-ignore-warnings": "^0.2.9", + "prettier": "^3.0.0", + "prettier-plugin-solidity": "^1.1.3", + "solhint": "^3.4.1", + "solhint-plugin-openzeppelin": "file:scripts/solhint-custom", + "solidity-coverage": "^0.7.22" + } +} diff --git a/scripts/deploy.ts b/scripts/deploy.ts new file mode 100644 index 0000000..9e098e5 --- /dev/null +++ b/scripts/deploy.ts @@ -0,0 +1,36 @@ +import "@nomiclabs/hardhat-ethers"; +import { ethers, network } from "hardhat"; + +async function main() { + // This is just a convenience check + if (network.name === "hardhat") { + console.warn( + "You are trying to deploy a contract to the Hardhat Network, which" + + "gets automatically created and destroyed every time. Use the Hardhat" + + " option '--network localhost'" + ); + } + + // ethers is available in the global scope + const [deployer] = await ethers.getSigners(); + console.log( + "Deploying the contracts with the account:", + await deployer.getAddress() + ); + + console.log("Account balance:", (await deployer.getBalance()).toString()); + + const Token = await ethers.getContractFactory("GigacountsToken"); + const token = await Token.deploy(); + await token.deployed(); + + console.log("Token address:", token.address); + +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); \ No newline at end of file diff --git a/scripts/interact.ts b/scripts/interact.ts new file mode 100644 index 0000000..ce8a86e --- /dev/null +++ b/scripts/interact.ts @@ -0,0 +1,23 @@ +import "@nomiclabs/hardhat-ethers"; +import { ethers } from "hardhat"; +import dotenv from "dotenv"; + +dotenv.config() + +async function main() { + console.log('Getting the gigacounts token contract...'); + const contractAddress = process.env.DEPLOYED_GIGACOUNTS_CONTRACT_ADDRESS || '0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9'; + console.log(contractAddress) + const token = await ethers.getContractAt('GigacountsToken', contractAddress); + const tokenName = await token.name(); + const tokenSymbol = await token.name(); + console.log(`Token Name: ${tokenName}\n`); + console.log(`Token Symbol: ${tokenSymbol}\n`); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exitCode = 1; + }); \ No newline at end of file diff --git a/slither.config.json b/slither.config.json new file mode 100644 index 0000000..da2ca93 --- /dev/null +++ b/slither.config.json @@ -0,0 +1,5 @@ +{ + "detectors_to_run": "arbitrary-send-erc20,array-by-reference,incorrect-shift,name-reused,rtlo,suicidal,uninitialized-state,uninitialized-storage,arbitrary-send-erc20-permit,controlled-array-length,controlled-delegatecall,delegatecall-loop,msg-value-loop,reentrancy-eth,unchecked-transfer,weak-prng,domain-separator-collision,erc20-interface,erc721-interface,locked-ether,mapping-deletion,shadowing-abstract,tautology,write-after-write,boolean-cst,reentrancy-no-eth,reused-constructor,tx-origin,unchecked-lowlevel,unchecked-send,variable-scope,void-cst,events-access,events-maths,incorrect-unary,boolean-equal,cyclomatic-complexity,deprecated-standards,erc20-indexed,function-init-state,pragma,unused-state,reentrancy-unlimited-gas,constable-states,immutable-states,var-read-using-this", + "filter_paths": "contracts/mocks,contracts-exposed", + "compile_force_framework": "hardhat" +} \ No newline at end of file diff --git a/solhint.config.js b/solhint.config.js new file mode 100644 index 0000000..fe44245 --- /dev/null +++ b/solhint.config.js @@ -0,0 +1,17 @@ +const rules = [ + 'no-unused-vars', + 'const-name-snakecase', + 'contract-name-camelcase', + 'event-name-camelcase', + 'func-name-mixedcase', + 'func-param-name-mixedcase', + 'modifier-name-mixedcase', + 'var-name-mixedcase', + 'imports-on-top', + 'no-global-import' +]; + +module.exports = { + plugins: ['openzeppelin'], + rules: Object.fromEntries(rules.map(r => [r, 'error'])) +}; \ No newline at end of file diff --git a/test/GigacountsToken.test.ts b/test/GigacountsToken.test.ts new file mode 100644 index 0000000..a647f4b --- /dev/null +++ b/test/GigacountsToken.test.ts @@ -0,0 +1,131 @@ +const { expect } = require('chai'); +const { ethers } = require("hardhat"); + +describe('GigacountsToken Unit Tests', function () { + before(async function () { + this.GigacountsToken = await ethers.getContractFactory('GigacountsToken'); + }); + + beforeEach(async function () { + this.gigacountsToken = await this.GigacountsToken.deploy(); + await this.gigacountsToken.deployed(); + + this.decimals = await this.gigacountsToken.decimals(); + + const signers = await ethers.getSigners(); + + this.ownerAddress = signers[0].address; + this.recipientAddress = signers[1].address; + + this.signerContract = this.gigacountsToken.connect(signers[1]); + }); + + it('Creates a token with a name', async function () { + expect(await this.gigacountsToken.name()).to.exist; + }); + + it('Creates a token with a symbol', async function () { + expect(await this.gigacountsToken.symbol()).to.exist; + }); + + it('Has a valid decimal', async function () { + expect((await this.gigacountsToken.decimals()).toString()).to.equal('18'); + }) + + it('Has a valid total supply', async function () { + const expectedSupply = ethers.utils.parseUnits('100000000', this.decimals); + expect((await this.gigacountsToken.totalSupply()).toString()).to.equal(expectedSupply); + }); + + it('Is able to query account balances', async function () { + const ownerBalance = await this.gigacountsToken.balanceOf(this.ownerAddress); + expect(await this.gigacountsToken.balanceOf(this.ownerAddress)).to.equal(ownerBalance); + }); + + it('Transfers the right amount of tokens to/from an account', async function () { + const transferAmount = 1000; + await expect(this.gigacountsToken.transfer(this.recipientAddress, transferAmount)).to.changeTokenBalances( + this.gigacountsToken, + [this.ownerAddress, this.recipientAddress], + [-transferAmount, transferAmount] + ); + }); + + it('Emits a transfer event with the right arguments', async function () { + const transferAmount = 100000; + await expect(this.gigacountsToken.transfer(this.recipientAddress, ethers.utils.parseUnits(transferAmount.toString(), this.decimals))) + .to.emit(this.gigacountsToken, "Transfer") + .withArgs(this.ownerAddress, this.recipientAddress, ethers.utils.parseUnits(transferAmount.toString(), this.decimals)) + }); + + it('Allows for allowance approvals and queries', async function () { + const approveAmount = 10000; + await this.signerContract.approve(this.ownerAddress, ethers.utils.parseUnits(approveAmount.toString(), this.decimals)); + expect((await this.gigacountsToken.allowance(this.recipientAddress, this.ownerAddress))).to.equal(ethers.utils.parseUnits(approveAmount.toString(), this.decimals)); + }); + + it('Emits an approval event with the right arguments', async function () { + const approveAmount = 10000; + await expect(this.signerContract.approve(this.ownerAddress, ethers.utils.parseUnits(approveAmount.toString(), this.decimals))) + .to.emit(this.gigacountsToken, "Approval") + .withArgs(this.recipientAddress, this.ownerAddress, ethers.utils.parseUnits(approveAmount.toString(), this.decimals)) + }); + + it('Allows an approved spender to transfer from owner', async function () { + const transferAmount = 10000; + await this.gigacountsToken.transfer(this.recipientAddress, ethers.utils.parseUnits(transferAmount.toString(), this.decimals)) + await this.signerContract.approve(this.ownerAddress, ethers.utils.parseUnits(transferAmount.toString(), this.decimals)) + await expect(this.gigacountsToken.transferFrom(this.recipientAddress, this.ownerAddress, transferAmount)).to.changeTokenBalances( + this.gigacountsToken, + [this.ownerAddress, this.recipientAddress], + [transferAmount, -transferAmount] + ); + }); + + it('Emits a transfer event with the right arguments when conducting an approved transfer', async function () { + const transferAmount = 10000; + await this.gigacountsToken.transfer(this.recipientAddress, ethers.utils.parseUnits(transferAmount.toString(), this.decimals)) + await this.signerContract.approve(this.ownerAddress, ethers.utils.parseUnits(transferAmount.toString(), this.decimals)) + await expect(this.gigacountsToken.transferFrom(this.recipientAddress, this.ownerAddress, ethers.utils.parseUnits(transferAmount.toString(), this.decimals))) + .to.emit(this.gigacountsToken, "Transfer") + .withArgs(this.recipientAddress, this.ownerAddress, ethers.utils.parseUnits(transferAmount.toString(), this.decimals)) + }); + + it('Allows allowance to be increased and queried', async function () { + const initialAmount = 100; + const incrementAmount = 10000; + await this.signerContract.approve(this.ownerAddress, ethers.utils.parseUnits(initialAmount.toString(), this.decimals)) + const previousAllowance = await this.gigacountsToken.allowance(this.recipientAddress, this.ownerAddress); + await this.signerContract.increaseAllowance(this.ownerAddress, ethers.utils.parseUnits(incrementAmount.toString(), this.decimals)); + const expectedAllowance = ethers.BigNumber.from(previousAllowance).add(ethers.BigNumber.from(ethers.utils.parseUnits(incrementAmount.toString(), this.decimals))) + expect((await this.gigacountsToken.allowance(this.recipientAddress, this.ownerAddress))).to.equal(expectedAllowance); + }); + + it('Emits approval event when alllowance is increased', async function () { + const incrementAmount = 10000; + await expect(this.signerContract.increaseAllowance(this.ownerAddress, ethers.utils.parseUnits(incrementAmount.toString(), this.decimals))) + .to.emit(this.gigacountsToken, "Approval") + .withArgs(this.recipientAddress, this.ownerAddress, ethers.utils.parseUnits(incrementAmount.toString(), this.decimals)) + }); + + it('Allows allowance to be decreased and queried', async function () { + const initialAmount = 100; + const decrementAmount = 10; + await this.signerContract.approve(this.ownerAddress, ethers.utils.parseUnits(initialAmount.toString(), this.decimals)) + const previousAllowance = await this.gigacountsToken.allowance(this.recipientAddress, this.ownerAddress); + await this.signerContract.decreaseAllowance(this.ownerAddress, ethers.utils.parseUnits(decrementAmount.toString(), this.decimals)); + const expectedAllowance = ethers.BigNumber.from(previousAllowance).sub(ethers.BigNumber.from(ethers.utils.parseUnits(decrementAmount.toString(), this.decimals))) + expect((await this.gigacountsToken.allowance(this.recipientAddress, this.ownerAddress))).to.equal(expectedAllowance); + }); + + it('Emits approval event when alllowance is decreased', async function () { + const initialAmount = 100; + const decrementAmount = 10; + await this.signerContract.approve(this.ownerAddress, ethers.utils.parseUnits(initialAmount.toString(), this.decimals)) + const expectedAllowance = ethers.BigNumber.from(ethers.utils.parseUnits(initialAmount.toString(), this.decimals)).sub(ethers.BigNumber.from(ethers.utils.parseUnits(decrementAmount.toString(), this.decimals))) + await expect(this.signerContract.decreaseAllowance(this.ownerAddress, ethers.utils.parseUnits(decrementAmount.toString(), this.decimals))) + .to.emit(this.gigacountsToken, "Approval") + .withArgs(this.recipientAddress, this.ownerAddress, expectedAllowance) + }); + +}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ea56dc5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "outDir": "dist", + "resolveJsonModule": true + }, + "include": ["./test", "./typechain"], + "exclude": ["node_modules"], + "files": ["./hardhat.config.ts"] +} \ No newline at end of file