From c54f37817a9652bae2548e1aca1d2b191702fbf9 Mon Sep 17 00:00:00 2001 From: UrfinDeuce Date: Thu, 21 Sep 2023 13:15:20 +0300 Subject: [PATCH] Used openzeppelin lib --- .github/workflows/build.yml | 23 ++ .github/workflows/coverage.yml | 24 ++ .github/workflows/lint.yml | 20 ++ .github/workflows/test.yml | 18 ++ contracts/ERC20.sol | 399 --------------------------------- contracts/MyERC20.sol | 52 +++++ package.json | 4 +- test/ERC20.ts | 10 +- yarn.lock | 5 + 9 files changed, 150 insertions(+), 405 deletions(-) create mode 100755 .github/workflows/build.yml create mode 100755 .github/workflows/coverage.yml create mode 100755 .github/workflows/lint.yml create mode 100755 .github/workflows/test.yml delete mode 100644 contracts/ERC20.sol create mode 100644 contracts/MyERC20.sol diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100755 index 0000000..f3dd6fc --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,23 @@ +name: build +on: push + +jobs: + coverage: + name: build + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v3 + - name: setup-node + uses: actions/setup-node@v3 + with: + node-version: 16 + - name: install + run: yarn install + - name: build + run: yarn hardhat compile + - name: upload artifacts + uses: actions/upload-artifact@v1 + with: + name: artifacts + path: artifacts/contracts diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100755 index 0000000..86530a8 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,24 @@ +name: coverage +on: push + +jobs: + coverage: + name: coverage + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v3 + - name: setup-node + uses: actions/setup-node@v3 + with: + node-version: 16 + - name: install + run: yarn install + - name: test+coverage + run: yarn hardhat coverage + - name: upload artifacts + uses: actions/upload-artifact@v1 + if: always() + with: + name: coverage + path: coverage diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100755 index 0000000..4c42d3d --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,20 @@ +name: lint +on: push + +jobs: + lint: + name: lint + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v3 + - name: setup-node + uses: actions/setup-node@v3 + with: + node-version: 16 + - name: install + run: yarn install + - name: lint ts + run: yarn eslint . + - name: lint sol + run: yarn prettier . diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100755 index 0000000..c8ef2ba --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,18 @@ +name: test +on: push + +jobs: + coverage: + name: test + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v3 + - name: setup-node + uses: actions/setup-node@v3 + with: + node-version: 16 + - name: install + run: yarn install + - name: test + run: yarn hardhat test diff --git a/contracts/ERC20.sol b/contracts/ERC20.sol deleted file mode 100644 index d20cc6e..0000000 --- a/contracts/ERC20.sol +++ /dev/null @@ -1,399 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -/** - * It is a broken (!!!) ERC-20 implementation based of Open Zeppelin contract. It is used as a test task for a trainee - * solidity QA position. Do not use in any other cases! - */ -contract ERC20 { - - /** - * @dev Emitted when `value` tokens are moved from one account (`from`) to - * another (`to`). - * - * Note that `value` may be zero. - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @dev Emitted when the allowance of a `spender` for an `owner` is set by - * a call to {approve}. `value` is the new allowance. - */ - event Approval(address indexed owner, address indexed spender, uint256 value); - - // Mapping user address => user token balance - mapping(address => uint256) private _balances; - - // Mapping token owner address => - // user allowed to operate with token owner tokens => - // amount of tokens allowed to operate - mapping(address => mapping(address => uint256)) private _allowances; - - // Total tokens supply - uint256 private _totalSupply; - - // Token price - uint256 private _price; - - // Token name - string private _name; - - // Token symbol - string private _symbol; - - /** - * @dev Sets the values for {name} {symbol} and {price}. - * - * All of these values are immutable: they can only be set once during - * construction. - */ - constructor(string memory name_, string memory symbol_, uint256 price_) { - _name = name_; - _symbol = symbol_; - _price = price_; - } - - /** - * @dev Returns the name of the token. - */ - function name() public view virtual returns (string memory) { - return _name; - } - - /** - * @dev Returns the symbol of the token, usually a shorter version of the - * name. - */ - function symbol() public view virtual returns (string memory) { - return _symbol; - } - - /** - * @dev Returns the price of the token - */ - function price() public view returns (uint256) { - return _price; - } - - /** - * @dev Returns the number of decimals used to get its user representation. - * For example, if `decimals` equals `2`, a balance of `505` tokens should - * be displayed to a user as `5.05` (`505 / 10 ** 2`). - * - * Tokens usually opt for a value of 18, imitating the relationship between - * Ether and Wei. This is the default value returned by this function, unless - * it's overridden. - * - * NOTE: This information is only used for _display_ purposes: it in - * no way affects any of the arithmetic of the contract, including - * {IERC20-balanceOf} and {IERC20-transfer}. - */ - function decimals() public view virtual returns (uint8) { - return 18; - } - - /** - * @dev returns total token supply - */ - function totalSupply() public view virtual returns (uint256) { - return _totalSupply; - } - - /** - * @dev returns token balance for given user address - */ - function balanceOf(address account) public view virtual returns (uint256) { - return _balances[account]; - } - - /** - * Mints `amount` of tokens to the caller - * - * Requirements: - * - * - the caller must have a balance of at least `amount`. - */ - function mint(uint256 amount) external payable { - require(msg.value >= amount * _price, "ERC20: value is less than required"); - _mint(msg.sender, amount); - } - - /** - * @dev Destroys `amount` tokens from caller, reducing the - * total supply. - * - * Emits a {Transfer} event with `to` set to the zero address. - * - * Requirements: - * - * - message sender must have at least `amount` tokens. - */ - function burn(uint256 amount) external { - _burn(msg.sender, amount); - } - - /** - * Transfers `amount` of tokens `to` address - * - * Requirements: - * - * - `to` cannot be the zero address. - * - the caller must have a balance of at least `amount`. - */ - function transfer(address to, uint256 amount) public virtual returns (bool) { - _transfer(msg.sender, to, amount); - return true; - } - - /** - * Returns allowance amount given from `owner` to `spender` - */ - function allowance(address owner, address spender) public view virtual returns (uint256) { - return _allowances[owner][spender]; - } - - /** - * Set approval for given token `amount` for given `spender` - * - * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on - * `transferFrom`. This is semantically equivalent to an infinite approval. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function approve(address spender, uint256 amount) public virtual returns (bool) { - address owner = msg.sender; - _approve(owner, spender, amount); - return true; - } - - /** - * Transfers `amount` of tokens `from` and `to` given addresses - * - * Emits an {Approval} event indicating the updated allowance. This is not - * required by the EIP. See the note at the beginning of {ERC20}. - * - * NOTE: Does not update the allowance if the current allowance - * is the maximum `uint256`. - * - * Requirements: - * - * - `from` and `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - * - the caller must have allowance for ``from``'s tokens of at least - * `amount`. - */ - function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { - address spender = msg.sender; - _spendAllowance(from, spender, amount); - _transfer(from, to, amount); - return true; - } - - /** - * @dev Atomically increases the allowance granted to `spender` by the caller. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { - address owner = msg.sender; - uint256 currentAllowance = allowance(owner, spender); - require( - currentAllowance + addedValue >= currentAllowance, - "ERC20: increased allowance exceeds type(uint256).max" - ); - unchecked { - _approve(owner, spender, currentAllowance + addedValue); - } - - return true; - } - - /** - * @dev Atomically decreases the allowance granted to `spender` by the caller. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `spender` must have allowance for the caller of at least - * `subtractedValue`. - */ - function decreaseAllowance( - address spender, - uint256 subtractedValue - ) public virtual returns (bool) { - address owner = msg.sender; - uint256 currentAllowance = allowance(owner, spender); - require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); - unchecked { - _approve(owner, spender, currentAllowance - subtractedValue); - } - - return true; - } - - /** - * @dev Moves `amount` of tokens from `from` to `to`. - * - * This internal function is equivalent to {transfer}, and can be used to - * e.g. implement automatic token fees, slashing mechanisms, etc. - * - * Emits a {Transfer} event. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - */ - function _transfer(address from, address to, uint256 amount) internal virtual { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); - - _beforeTokenTransfer(from, to, amount); - - uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); - unchecked { - _balances[from] -= amount; - // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by - // decrementing then incrementing. - _balances[to] += amount; - } - - emit Transfer(from, to, amount); - - _afterTokenTransfer(from, to, amount); - } - - /** @dev Creates `amount` tokens and assigns them to `account`, increasing - * the total supply. - * - * Emits a {Transfer} event with `from` set to the zero address. - * - * Requirements: - * - * - `account` cannot be the zero address. - */ - function _mint(address account, uint256 amount) internal virtual { - require(account != address(0), "ERC20: mint to the zero address"); - - _beforeTokenTransfer(address(0), account, amount); - - _totalSupply += amount; - unchecked { - // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. - _balances[account] += amount; - } - emit Transfer(address(0), account, amount); - - _afterTokenTransfer(address(0), account, amount); - } - - /** - * @dev Destroys `amount` tokens from `account`, reducing the - * total supply. - * - * Emits a {Transfer} event with `to` set to the zero address. - * - * Requirements: - * - * - `account` cannot be the zero address. - * - `account` must have at least `amount` tokens. - */ - function _burn(address account, uint256 amount) internal virtual { - require(account != address(0), "ERC20: burn from the zero address"); - - _beforeTokenTransfer(account, address(0), amount); - - uint256 accountBalance = _balances[account]; - require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); - unchecked { - _balances[account] = accountBalance - amount; - // Overflow not possible: amount <= accountBalance <= totalSupply. - _totalSupply -= amount; - } - - emit Transfer(account, address(0), amount); - - _afterTokenTransfer(account, address(0), amount); - } - - /** - * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. - * - * This internal function is equivalent to `approve`, and can be used to - * e.g. set automatic allowances for certain subsystems, etc. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `owner` cannot be the zero address. - * - `spender` cannot be the zero address. - */ - function _approve(address owner, address spender, uint256 amount) internal virtual { - require(owner != address(0), "ERC20: approve from the zero address"); - require(spender != address(0), "ERC20: approve to the zero address"); - - _allowances[owner][spender] = amount; - emit Approval(owner, spender, amount); - } - - /** - * @dev Updates `owner` s allowance for `spender` based on spent `amount`. - * - * Does not update the allowance amount in case of infinite allowance. - * Revert if not enough allowance is available. - * - * Might emit an {Approval} event. - */ - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { - require(currentAllowance >= amount, "ERC20: insufficient allowance"); - unchecked { - _approve(owner, spender, currentAllowance - amount); - } - } - } - - /** - * @dev Hook that is called before any transfer of tokens. This includes - * minting and burning. - * - * Calling conditions: - * - * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * will be transferred to `to`. - * - when `from` is zero, `amount` tokens will be minted for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} - - /** - * @dev Hook that is called after any transfer of tokens. This includes - * minting and burning. - * - * Calling conditions: - * - * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * has been transferred to `to`. - * - when `from` is zero, `amount` tokens have been minted for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens have been burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} -} diff --git a/contracts/MyERC20.sol b/contracts/MyERC20.sol new file mode 100644 index 0000000..7ddc903 --- /dev/null +++ b/contracts/MyERC20.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MyERC20 is ERC20 { + // Token price + uint256 private _price; + + /** + * @dev Sets the values for {name} {symbol} and {price}. + * + * All of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_, uint256 price_) ERC20(name_, symbol_) { + _price = price_; + } + + /** + * @dev Returns the price of the token + */ + function price() public view returns (uint256) { + return _price; + } + + /** + * Mints `amount` of tokens to the caller + * + * Requirements: + * + * - the caller must have a balance of at least `amount`. + */ + function mint(uint256 amount) external payable { + require(msg.value >= amount * _price, "ERC20: value is less than required"); + _mint(msg.sender, amount); + } + + /** + * @dev Destroys `amount` tokens from caller, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - message sender must have at least `amount` tokens. + */ + function burn(uint256 amount) external { + _burn(msg.sender, amount); + } +} diff --git a/package.json b/package.json index 1fced1c..080853e 100644 --- a/package.json +++ b/package.json @@ -33,5 +33,7 @@ "typechain": "^8.1.0", "typescript": "^5.2.2" }, - "dependencies": {} + "dependencies": { + "@openzeppelin/contracts": "^4.9.3" + } } diff --git a/test/ERC20.ts b/test/ERC20.ts index 49723aa..6647621 100644 --- a/test/ERC20.ts +++ b/test/ERC20.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { ethers } from 'hardhat'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { type ERC20 } from '../typechain-types'; +import { type MyERC20 } from '../typechain-types/contracts/MyERC20'; import { type SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; const PRICE = ethers.BigNumber.from(1000000000); @@ -9,17 +9,17 @@ const AMOUNT = ethers.BigNumber.from(1000); const NAME = 'MyToken'; const SYMBOL = 'MTK'; -describe('ERC20', function () { +describe('MyERC20', function () { async function deployContractsFixture(): Promise<{ - token: ERC20; + token: MyERC20; user: SignerWithAddress; from: SignerWithAddress; to: SignerWithAddress; owner: SignerWithAddress; spender: SignerWithAddress; }> { - const ERC20 = await ethers.getContractFactory('ERC20'); - const token = await ERC20.deploy(NAME, SYMBOL, PRICE); + const MyERC20 = await ethers.getContractFactory('MyERC20'); + const token = await MyERC20.deploy(NAME, SYMBOL, PRICE); const [owner, spender, user, from, to] = await ethers.getSigners(); return { token, user, from, to, owner, spender }; diff --git a/yarn.lock b/yarn.lock index 5efd766..40bb239 100644 --- a/yarn.lock +++ b/yarn.lock @@ -717,6 +717,11 @@ table "^6.8.0" undici "^5.14.0" +"@openzeppelin/contracts@^4.9.3": + version "4.9.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.3.tgz#00d7a8cf35a475b160b3f0293a6403c511099364" + integrity sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg== + "@scure/base@~1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938"