├── .prettierrc
├── docs
├── audit2.pdf
├── audit3-Evaluation report on H01 mitigation.md
├── audit3.md
└── audit1.md
├── .gitignore
├── .mocharc.json
├── waffle.json
├── tsconfig.json
├── hardhat.config.ts
├── contracts
├── test
│ ├── token.sol
│ ├── token_safe_transfer.sol
│ └── uniswap_v2_pair.sol
├── interfaces
│ ├── IUniswapV2Pair.sol
│ └── IDePayLiquidityStaking.sol
└── DePayLiquidityStaking.sol
├── flatten
├── IUniswapV2Pair.sol
├── IDePayLiquidityStaking.sol
├── token.sol
├── token_safe_transfer.sol
├── uniswap_v2_pair.sol
└── DePayLiquidityStaking.sol
├── package.json
├── README.md
└── test
└── DePayLiquidityStaking.spec.ts
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "printWidth": 120
5 | }
6 |
--------------------------------------------------------------------------------
/docs/audit2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DePayFi/depay-ethereum-staking/HEAD/docs/audit2.pdf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | build/
3 | artifacts/
4 | yarn-error.log
5 | cache/
6 | typechain/
7 |
--------------------------------------------------------------------------------
/.mocharc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extension": ["ts"],
3 | "spec": "./test/**/*.spec.ts",
4 | "require": "ts-node/register",
5 | "timeout": 12000
6 | }
7 |
--------------------------------------------------------------------------------
/waffle.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerType": "solcjs",
3 | "compilerVersion": "0.7.5",
4 | "sourceDirectory": "./contracts",
5 | "outputDirectory": "./build"
6 | }
7 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "strict": true,
6 | "esModuleInterop": true,
7 | "resolveJsonModule": true
8 | },
9 | "include": ["./test"],
10 | "files": ["./hardhat.config.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/hardhat.config.ts:
--------------------------------------------------------------------------------
1 | import { HardhatUserConfig } from "hardhat/types";
2 | import "@nomiclabs/hardhat-waffle";
3 | import "hardhat-typechain";
4 |
5 | const config: HardhatUserConfig = {
6 | solidity: {
7 | compilers: [{ version: "0.7.5", settings: {} }],
8 | }
9 | };
10 |
11 | export default config;
12 |
--------------------------------------------------------------------------------
/contracts/test/token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | // used for running automated hardhat tests
4 |
5 | pragma solidity >=0.7.5 <0.8.0;
6 |
7 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
8 |
9 | contract Token is ERC20 {
10 |
11 | constructor() public ERC20("Token", "TKN") {
12 | _mint(msg.sender, 1000000000000000000000000);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/contracts/interfaces/IUniswapV2Pair.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.7.5 <0.8.0;
4 |
5 | interface IUniswapV2Pair {
6 |
7 | function totalSupply() external view returns (uint);
8 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
9 | function transfer(address to, uint value) external returns (bool);
10 | function transferFrom(address from, address to, uint value) external returns (bool);
11 | function token0() external view returns (address);
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/flatten/IUniswapV2Pair.sol:
--------------------------------------------------------------------------------
1 | // Root file: contracts/interfaces/IUniswapV2Pair.sol
2 |
3 | // SPDX-License-Identifier: MIT
4 |
5 | pragma solidity >=0.7.5 <0.8.0;
6 |
7 | interface IUniswapV2Pair {
8 |
9 | function totalSupply() external view returns (uint);
10 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
11 | function transfer(address to, uint value) external returns (bool);
12 | function transferFrom(address from, address to, uint value) external returns (bool);
13 | function token0() external view returns (address);
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/contracts/test/token_safe_transfer.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | // used for running automated hardhat tests
4 |
5 | pragma solidity >=0.7.5 <0.8.0;
6 |
7 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
8 |
9 | contract TokenSafeTransfer is ERC20 {
10 |
11 | bool private initialTransfer;
12 |
13 | constructor() public ERC20("TokenSafe", "TKNSF") {
14 | _mint(msg.sender, 1000000000000000000000000);
15 | }
16 |
17 | function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
18 | if(initialTransfer == false) {
19 | _transfer(_msgSender(), recipient, amount);
20 | initialTransfer = true;
21 | } else {
22 | require(false, 'Token transfer failed!'); // this is for testing safeTransfer
23 | }
24 | return true;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/contracts/test/uniswap_v2_pair.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | // used for running automated hardhat tests
4 |
5 | pragma solidity >=0.7.5 <0.8.0;
6 |
7 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
8 |
9 | contract UniswapV2Pair is ERC20 {
10 |
11 | uint112 private reserve0;
12 | uint112 private reserve1;
13 | address public token0;
14 |
15 | constructor(uint256 totalSupply, address _token0, uint112 _reserve0, uint112 _reserve1) public ERC20("Uniswap V2", "UNI-V2") {
16 | _mint(msg.sender, totalSupply);
17 | reserve0 = _reserve0;
18 | reserve1 = _reserve1;
19 | token0 = _token0;
20 | }
21 |
22 | function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
23 | _reserve0 = reserve0;
24 | _reserve1 = reserve1;
25 | _blockTimestampLast = uint32(block.timestamp);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@depay/ethereum-staking",
3 | "description": "Ethereum staking for DEPAY tokens.",
4 | "version": "1.0.0",
5 | "homepage": "https://depay.fi",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/depayfi/ethereum-staking"
9 | },
10 | "keywords": [
11 | "depay",
12 | "ethereum",
13 | "contracts",
14 | "staking",
15 | "decentralized",
16 | "defi"
17 | ],
18 | "files": [
19 | "contracts",
20 | "build"
21 | ],
22 | "engines": {
23 | "node": ">=10"
24 | },
25 | "devDependencies": {
26 | "@nomiclabs/hardhat-ethers": "^2.0.1",
27 | "@nomiclabs/hardhat-waffle": "^2.0.0",
28 | "@openzeppelin/contracts": "^3.2.0",
29 | "@types/chai": "^4.2.6",
30 | "@types/mocha": "^5.2.7",
31 | "chai": "^4.2.0",
32 | "ethereum-waffle": "^3.2.1",
33 | "ethers": "^5.0.23",
34 | "hardhat": "^2.0.4",
35 | "hardhat-typechain": "^0.3.3",
36 | "mocha": "^6.2.2",
37 | "prettier": "^1.19.1",
38 | "rimraf": "^3.0.0",
39 | "solc": "0.5.16",
40 | "ts-generator": "^0.1.1",
41 | "ts-node": "^8.5.4",
42 | "typechain": "^4.0.0",
43 | "typechain-target-ethers-v5": "^1.2.2",
44 | "typescript": "^3.7.3"
45 | },
46 | "scripts": {
47 | "lint": "yarn prettier ./test/*.ts --check",
48 | "lint:fix": "yarn prettier ./test/*.ts --write",
49 | "clean": "rimraf ./build/",
50 | "precompile": "yarn clean",
51 | "compile": "npx hardhat compile",
52 | "pretest": "yarn compile",
53 | "test": "npx mocha",
54 | "prepublishOnly": "yarn test",
55 | "build": "npx hardhat compile",
56 | "flatten": "waffle flatten"
57 | },
58 | "license": "MIT"
59 | }
60 |
--------------------------------------------------------------------------------
/contracts/interfaces/IDePayLiquidityStaking.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.7.5 <0.8.0;
4 |
5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6 | import './IUniswapV2Pair.sol';
7 |
8 | interface IDePayLiquidityStaking {
9 |
10 | function startTime() external view returns (uint256);
11 | function closeTime() external view returns (uint256);
12 | function releaseTime() external view returns (uint256);
13 | function rewardsAmount() external view returns (uint256);
14 | function percentageYield() external view returns (uint256);
15 | function allocatedStakingRewards() external view returns (uint256);
16 |
17 | function token() external view returns (IERC20);
18 | function liquidityToken() external view returns (IUniswapV2Pair);
19 |
20 | function unstakeEarlyAllowed() external view returns (bool);
21 |
22 | function rewardsPerAddress(address) external view returns (uint256);
23 | function stakedLiquidityTokenPerAddress(address) external view returns (uint256);
24 |
25 | function tokenReserveOnInit() external view returns (uint256);
26 | function liquidityTokenTotalSupplyOnInit() external view returns (uint256);
27 |
28 | function init(
29 | uint256 _startTime,
30 | uint256 _closeTime,
31 | uint256 _releaseTime,
32 | uint256 _percentageYield,
33 | address _liquidityToken,
34 | address _token
35 | ) external returns(bool);
36 |
37 | function stake(
38 | uint256 stakedLiquidityTokenAmount
39 | ) external returns(bool);
40 |
41 | function withdraw(
42 | address tokenAddress,
43 | uint amount
44 | ) external returns(bool);
45 |
46 | function unstake() external returns(bool);
47 |
48 | function enableUnstakeEarly() external returns(bool);
49 |
50 | function unstakeEarly() external returns(bool);
51 |
52 | function destroy() external returns(bool);
53 | }
54 |
--------------------------------------------------------------------------------
/docs/audit3-Evaluation report on H01 mitigation.md:
--------------------------------------------------------------------------------
1 | # Evaluation report on H01 mitigation
2 | | Name | Information |
3 | |:----------:|-----------|
4 | | Repository | https://github.com/DePayFi/ethereum-staking |
5 | | Checked | [ cbe9cd589f3f85ef4f3074aada47039812667d3f](https://github.com/DePayFi/ethereum-staking/commit/cbe9cd589f3f85ef4f3074aada47039812667d3f) |
6 | | Branch | [master](https://github.com/DePayFi/ethereum-staking) |
7 | | Time | Thu, 14 Dec 2020 03:59:35 UTC |
8 | | Author | Temitayo Daniel|
9 |
10 | # Result
11 |
12 | | Severity | Count | Link |
13 | |:--------:|----------:|------|
14 | | High | 1 | |
15 | |||[H01 - reward can be increased based on liquidity ](#H01)|
16 |
17 |
18 |
19 |
20 |
21 | ## H01 - reward can be increased based on liquidity
22 |
23 | | Affected | Severity | Count | Lines |
24 | |:---------------:|:----------|------:|-------:|
25 | | DePayLiquidityStaking.sol | High | 1 | [124-128](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L124)|
26 |
27 | Mitigated via [pull](https://github.com/DePayFi/ethereum-staking/commit/cbe9cd589f3f85ef4f3074aada47039812667d3f) by replacing the variables `reserve0` and `liquidityToken.totalSupply()` with `tokenReserveOnInit` and `liquidityTokenTotalSupplyOnInit` respectively.
28 |
29 | H01 was fully mitigated by initializing all parameters used for calculating `reward` in the `stake` right from the beginning of the liquidity pool. This means a user cannot influence the output of `rewards` in the `stake` function.
30 |
31 | If the `stake` function uses these initialized parameters in its calculation algorithm, then there is no avenue for a change in the output of `rewards` .
32 |
33 | H01 has been fully mitigated and no further action is required
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Mainnet
2 |
3 | - December 2020-2021: https://etherscan.io/address/0xe52c3ea072b3388dfa272d3c32723b8c5a232d90
4 | - January 2021-2022: https://etherscan.io/address/0x21a029a338005f0a8F3A2F4a0CFb7D7f91b56e65
5 | - February 2021-2022: https://etherscan.io/address/0x98eaa281b5606a19c7f7ef966d5fb687bca360f4
6 | - March 2021-2022: https://etherscan.io/address/0x126332e41655e313205e962cd0cb8041b2bdae2a
7 | - April 2021-2022: https://etherscan.io/address/0x7d4735d924995ed437c413b69814ec49e904b81e
8 | - May 2021-2022: https://etherscan.io/address/0xaded2aee9dabecbbcfc7abcf8cda03dd57e1f2af
9 |
10 | Ropsten:
11 |
12 | - https://ropsten.etherscan.io/address/0x7dfbf9d839e664cde859cabc6449fc904d4e8be0
13 |
14 | ## Quick Start
15 |
16 | ```
17 | yarn install
18 | yarn test
19 | ```
20 |
21 | ## Summary
22 |
23 | This contract enables `liquidityToken` staking and rewards stakers with the underlying reserve `token`
24 | which needs to be stored in the contract before initialization.
25 |
26 | From `startTime` to `closeTime` people can stake `liquidityToken` of the reserve `token` to earn `rewardAmounts` as configured with `percentageYield`
27 | calculated with the reserve amount the moment the `liquidityToken` is staked.
28 |
29 | Once `releaseTime` is reached every staker can `unstake` their locked `liquidityToken` which is automatically unstaked together with the rewards the moment you `unstake`.
30 |
31 | `unstakeEarly` is implemented to potentially migrate to Uniswap v3 in case it's released during the lockup.
32 |
33 | ## Features
34 |
35 | ### Init: Initialize Staking
36 |
37 | Once rewards have been stored, allows you to initalize the staking contract with:
38 |
39 | `_startTime: Epoch timestamp when staking starts.`
40 | `_closeTime: Epoch timestamp when entering staking ends.`
41 | `_releaseTime: Epoch timestamp when staking can be released with rewards.`
42 | `_percentageYield: Percentage yield on your intially staked token, calculated based on liquidity token reserves of that token the moment you stake your liquidity.`
43 | `_rewardsAmount: Total amount of rewards available and required to be stored before initalization.`
44 | `_liquidityToken: Address of the liquidity token.`
45 | `_token: Address of the reward token (needs to be the liquidity reserve token too).`
46 |
47 | https://ropsten.etherscan.io/tx/0x755af4c537afd38f547b569bbe32dba9149340d0f1757982512761ee0537187a
48 |
49 | If rewards haven't been stored upfront, staking will fail because every staking attempt would overflow rewardsAmount.
50 |
51 | ### Stake: Stake liquidity tokens for token rewards
52 |
53 | Requires you to have added liquidity to uniswap first:
54 |
55 | https://ropsten.etherscan.io/tx/0x9d44c22a6b9615bcc465bce4f26466d9b844660e8b5e5892a03ffb7431bebf3e
56 |
57 | Requires approval to deposit liquidity tokens within the staking contract:
58 |
59 | https://ropsten.etherscan.io/tx/0xaedc9d104b998332fd27b9ef4ad8fbd4a3dd3dbc24978fb63d263c2bb7373fdc
60 |
61 | Allows you to stake liquidity which assigns you rewards right away:
62 |
63 | https://ropsten.etherscan.io/tx/0x26d5f4fd4180d82753e122a1f52daae287d8634f134f0826a978e5138327cc2f
64 |
65 | Does not allow you to stake if staking hasn't started yet:
66 |
67 | https://ropsten.etherscan.io/tx/0xc6172aa6aa8d580f6344be1fa0daa29ceeef1443abca0f550b267460ced6853e
68 |
69 | Does not allow you to stake if staking has been closed already:
70 |
71 | https://ropsten.etherscan.io/tx/0x9301a7c24274d50b7cfe305cc4729df00542c6e942f88385c03e7edace9ce606
72 |
73 | Does not allow you to stake if staking rewards would overflow/overallocate:
74 |
75 | https://ropsten.etherscan.io/tx/0xe81e8ed21c26fc649c600e82b5973126a68c5d86052fe1cc179df30968de3606
76 |
77 | ### Withdraw: Roll-over unused reward tokens and accidently sent tokens
78 |
79 | Allows to withdrawal unallocated reward tokens to roll them over to the next months staking contract:
80 |
81 | https://ropsten.etherscan.io/tx/0x0ccc3b067f48ee3860ed1183acd7678346e69d0a24e65e46270496152f334c1d
82 |
83 | Does not allow you to withdrawal allocated reward tokens:
84 |
85 | https://ropsten.etherscan.io/tx/0x7af649af60b4585915f1d49c07e2daab9f18226d593b0f7ad8adb58d3331b1b0
86 |
87 | Does not allow you to withdrawal any liquidity tokens as they always belong to the stakers:
88 |
89 | https://ropsten.etherscan.io/tx/0x77e54ec05cbcd339309c862607ae07d4a804d67fc212f835df95f5b2948b13b2
90 |
91 | ### Unstake: Unstake your liquidity and receive rewards
92 |
93 | https://ropsten.etherscan.io/tx/0x252ad2aa83c968b0b4c9dc8c735574cf67a1ae3c2ca4fb02b31153ddd9d59cf0
94 |
95 | Does not allow you to unstake if staking is not releasable yet.
96 |
97 | https://ropsten.etherscan.io/tx/0xf64d19e655996762d2c74a82fe06c61c12d768169b033dd67c9bc44beb648076
98 |
99 | ### Enable Unstake Early: Initiate liquidity migration to Uniswap v3
100 |
101 | Allows to enable unstaking early to migrate to uniswap v3:
102 |
103 | https://ropsten.etherscan.io/tx/0x7777e6b80dce93ebc99dcf9fa469d4ea4bcc3628e02185b8e13faaf5f2143ea9
104 |
105 | ### Unstake Early: In order to migrate liquidity to Uniswap v3 together
106 |
107 | Executing early unstake gives you back your liquidity token so that you can migrate it to v3 and stake it with us again:
108 |
109 | https://ropsten.etherscan.io/tx/0xe4c29b8ced21e43d62b2ee5ed3072594d7d13487876d53efc6faa26506f841ab
110 |
111 | Does not allow you to unstake early if not enabled (only if migrating to Uniswap v3):
112 |
113 | https://ropsten.etherscan.io/tx/0x1cdf93420bbbc3fe642b06f17e209cdbe0729cc4631e1e02b0b6fbe1dbdd3687
114 |
115 | ## Deploy
116 |
117 | 1. `yarn flatten`
118 |
119 | 2. Deploy flatten contract via https://remix.ethereum.org/
120 |
--------------------------------------------------------------------------------
/flatten/IDePayLiquidityStaking.sol:
--------------------------------------------------------------------------------
1 | // Dependency file: @openzeppelin/contracts/token/ERC20/IERC20.sol
2 |
3 | // SPDX-License-Identifier: MIT
4 |
5 | // pragma solidity >=0.6.0 <0.8.0;
6 |
7 | /**
8 | * @dev Interface of the ERC20 standard as defined in the EIP.
9 | */
10 | interface IERC20 {
11 | /**
12 | * @dev Returns the amount of tokens in existence.
13 | */
14 | function totalSupply() external view returns (uint256);
15 |
16 | /**
17 | * @dev Returns the amount of tokens owned by `account`.
18 | */
19 | function balanceOf(address account) external view returns (uint256);
20 |
21 | /**
22 | * @dev Moves `amount` tokens from the caller's account to `recipient`.
23 | *
24 | * Returns a boolean value indicating whether the operation succeeded.
25 | *
26 | * Emits a {Transfer} event.
27 | */
28 | function transfer(address recipient, uint256 amount) external returns (bool);
29 |
30 | /**
31 | * @dev Returns the remaining number of tokens that `spender` will be
32 | * allowed to spend on behalf of `owner` through {transferFrom}. This is
33 | * zero by default.
34 | *
35 | * This value changes when {approve} or {transferFrom} are called.
36 | */
37 | function allowance(address owner, address spender) external view returns (uint256);
38 |
39 | /**
40 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
41 | *
42 | * Returns a boolean value indicating whether the operation succeeded.
43 | *
44 | * // importANT: Beware that changing an allowance with this method brings the risk
45 | * that someone may use both the old and the new allowance by unfortunate
46 | * transaction ordering. One possible solution to mitigate this race
47 | * condition is to first reduce the spender's allowance to 0 and set the
48 | * desired value afterwards:
49 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
50 | *
51 | * Emits an {Approval} event.
52 | */
53 | function approve(address spender, uint256 amount) external returns (bool);
54 |
55 | /**
56 | * @dev Moves `amount` tokens from `sender` to `recipient` using the
57 | * allowance mechanism. `amount` is then deducted from the caller's
58 | * allowance.
59 | *
60 | * Returns a boolean value indicating whether the operation succeeded.
61 | *
62 | * Emits a {Transfer} event.
63 | */
64 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
65 |
66 | /**
67 | * @dev Emitted when `value` tokens are moved from one account (`from`) to
68 | * another (`to`).
69 | *
70 | * Note that `value` may be zero.
71 | */
72 | event Transfer(address indexed from, address indexed to, uint256 value);
73 |
74 | /**
75 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by
76 | * a call to {approve}. `value` is the new allowance.
77 | */
78 | event Approval(address indexed owner, address indexed spender, uint256 value);
79 | }
80 |
81 |
82 | // Dependency file: contracts/interfaces/IUniswapV2Pair.sol
83 |
84 |
85 | // pragma solidity >=0.7.5 <0.8.0;
86 |
87 | interface IUniswapV2Pair {
88 |
89 | function totalSupply() external view returns (uint);
90 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
91 | function transfer(address to, uint value) external returns (bool);
92 | function transferFrom(address from, address to, uint value) external returns (bool);
93 | function token0() external view returns (address);
94 |
95 | }
96 |
97 |
98 | // Root file: contracts/interfaces/IDePayLiquidityStaking.sol
99 |
100 |
101 | pragma solidity >=0.7.5 <0.8.0;
102 |
103 | // import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
104 | // import 'contracts/interfaces/IUniswapV2Pair.sol';
105 |
106 | interface IDePayLiquidityStaking {
107 |
108 | function startTime() external view returns (uint256);
109 | function closeTime() external view returns (uint256);
110 | function releaseTime() external view returns (uint256);
111 | function rewardsAmount() external view returns (uint256);
112 | function percentageYield() external view returns (uint256);
113 | function allocatedStakingRewards() external view returns (uint256);
114 |
115 | function token() external view returns (IERC20);
116 | function liquidityToken() external view returns (IUniswapV2Pair);
117 |
118 | function unstakeEarlyAllowed() external view returns (bool);
119 |
120 | function rewardsPerAddress(address) external view returns (uint256);
121 | function stakedLiquidityTokenPerAddress(address) external view returns (uint256);
122 |
123 | function tokenReserveOnInit() external view returns (uint256);
124 | function liquidityTokenTotalSupplyOnInit() external view returns (uint256);
125 |
126 | function init(
127 | uint256 _startTime,
128 | uint256 _closeTime,
129 | uint256 _releaseTime,
130 | uint256 _percentageYield,
131 | address _liquidityToken,
132 | address _token
133 | ) external returns(bool);
134 |
135 | function stake(
136 | uint256 stakedLiquidityTokenAmount
137 | ) external returns(bool);
138 |
139 | function withdraw(
140 | address tokenAddress,
141 | uint amount
142 | ) external returns(bool);
143 |
144 | function unstake() external returns(bool);
145 |
146 | function enableUnstakeEarly() external returns(bool);
147 |
148 | function unstakeEarly() external returns(bool);
149 |
150 | function destroy() external returns(bool);
151 | }
152 |
--------------------------------------------------------------------------------
/contracts/DePayLiquidityStaking.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.7.5 <0.8.0;
4 |
5 | import './interfaces/IUniswapV2Pair.sol';
6 |
7 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
8 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
9 | import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
10 | import "@openzeppelin/contracts/access/Ownable.sol";
11 | import "@openzeppelin/contracts/math/SafeMath.sol";
12 |
13 | contract DePayLiquidityStaking is Ownable, ReentrancyGuard {
14 |
15 | using SafeMath for uint256;
16 | using SafeERC20 for IERC20;
17 |
18 | // Epoch time when staking starts: People are allowed to stake
19 | uint256 public startTime;
20 |
21 | // Epoch time when staking has been closed: People are not allowed to stake anymore
22 | uint256 public closeTime;
23 |
24 | // Epoch time when staking releases staked liquidity + rewards: People can withdrawal
25 | uint256 public releaseTime;
26 |
27 | // Total amount of staking rewards available
28 | uint256 public rewardsAmount;
29 |
30 | // Percentage Yield, will be divided by 100, e.g. set 80 for 80%
31 | uint256 public percentageYield;
32 |
33 | // Total amount of already allocated staking rewards
34 | uint256 public allocatedStakingRewards;
35 |
36 | // Address of the Uniswap liquidity pair (token)
37 | IUniswapV2Pair public liquidityToken;
38 |
39 | // Address of the token used for rewards
40 | IERC20 public token;
41 |
42 | // Indicating if unstaking early is allowed or not
43 | // This is used to upgrade liquidity to uniswap v3
44 | bool public unstakeEarlyAllowed;
45 |
46 | // Stores all rewards per address
47 | mapping (address => uint256) public rewardsPerAddress;
48 |
49 | // Stores all amounts of staked liquidity tokens per address
50 | mapping (address => uint256) public stakedLiquidityTokenPerAddress;
51 |
52 | // Token Reserve On initialization, used to calculate rewards upon staking
53 | uint256 public tokenReserveOnInit;
54 |
55 | // Liquidity Token Total Supply on initialization, used to calculate rewards upon staking
56 | uint256 public liquidityTokenTotalSupplyOnInit;
57 |
58 | modifier onlyUnstarted() {
59 | require(
60 | startTime == 0 || block.timestamp < startTime,
61 | "Staking has already started!"
62 | );
63 | _;
64 | }
65 |
66 | modifier onlyStarted() {
67 | require(block.timestamp > startTime, "Staking has not yet started!");
68 | _;
69 | }
70 |
71 | modifier onlyUnclosed() {
72 | require(block.timestamp < closeTime, "Staking has been closed!");
73 | _;
74 | }
75 |
76 | modifier onlyReleasable() {
77 | require(block.timestamp > releaseTime, "Staking is not releasable yet!");
78 | _;
79 | }
80 |
81 | modifier onlyUnstakeEarly() {
82 | require(unstakeEarlyAllowed, "Unstaking early not allowed!");
83 | _;
84 | }
85 |
86 | modifier onlyDistributedRewards(){
87 | require(allocatedStakingRewards == 0, 'Rewards were not distributed yet!');
88 | _;
89 | }
90 |
91 | function init(
92 | uint256 _startTime,
93 | uint256 _closeTime,
94 | uint256 _releaseTime,
95 | uint256 _percentageYield,
96 | address _liquidityToken,
97 | address _token
98 | ) external onlyOwner onlyUnstarted returns(bool) {
99 | require(isContract(_token), '_token address needs to be a contract!');
100 | require(isContract(_liquidityToken), '_liquidityToken address needs to be a contract!');
101 | require(_startTime < _closeTime && _closeTime < _releaseTime, '_startTime needs to be before _closeTime needs to be before _releaseTime!');
102 |
103 | startTime = _startTime;
104 | closeTime = _closeTime;
105 | releaseTime = _releaseTime;
106 | percentageYield = _percentageYield;
107 | liquidityToken = IUniswapV2Pair(_liquidityToken);
108 | token = IERC20(_token);
109 | rewardsAmount = token.balanceOf(address(this));
110 |
111 | require(liquidityToken.token0() == address(token), 'Rewards must be calculated based on the reward token address!');
112 | (tokenReserveOnInit,,) = liquidityToken.getReserves();
113 | liquidityTokenTotalSupplyOnInit = liquidityToken.totalSupply();
114 |
115 | return true;
116 | }
117 |
118 | function stake(
119 | uint256 stakedLiquidityTokenAmount
120 | ) external onlyStarted onlyUnclosed nonReentrant returns(bool) {
121 | require(
122 | liquidityToken.transferFrom(msg.sender, address(this), stakedLiquidityTokenAmount),
123 | 'Depositing liquidity token failed!'
124 | );
125 |
126 | uint256 rewards = stakedLiquidityTokenAmount
127 | .mul(tokenReserveOnInit)
128 | .div(liquidityTokenTotalSupplyOnInit)
129 | .mul(percentageYield)
130 | .div(100);
131 |
132 | rewardsPerAddress[msg.sender] = rewardsPerAddress[msg.sender].add(rewards);
133 | stakedLiquidityTokenPerAddress[msg.sender] = stakedLiquidityTokenPerAddress[msg.sender].add(stakedLiquidityTokenAmount);
134 |
135 | allocatedStakingRewards = allocatedStakingRewards.add(rewards);
136 | require(allocatedStakingRewards <= rewardsAmount, 'Staking overflows rewards!');
137 |
138 | return true;
139 | }
140 |
141 | function payableOwner() view private returns(address payable) {
142 | return payable(owner());
143 | }
144 |
145 | function withdraw(
146 | address tokenAddress,
147 | uint amount
148 | ) external onlyOwner nonReentrant returns(bool) {
149 | require(tokenAddress != address(liquidityToken), 'Not allowed to withdrawal liquidity tokens!');
150 |
151 | if(tokenAddress == address(token)) {
152 | require(
153 | allocatedStakingRewards <= token.balanceOf(address(this)).sub(amount),
154 | 'Only unallocated staking rewards are allowed to be withdrawn for roll-over to next staking contract!'
155 | );
156 | rewardsAmount = rewardsAmount.sub(amount);
157 | }
158 |
159 | IERC20(tokenAddress).safeTransfer(payableOwner(), amount);
160 | return true;
161 | }
162 |
163 | function _unstakeLiquidity() private {
164 | uint256 liquidityTokenAmount = stakedLiquidityTokenPerAddress[msg.sender];
165 | stakedLiquidityTokenPerAddress[msg.sender] = 0;
166 | require(
167 | liquidityToken.transfer(msg.sender, liquidityTokenAmount),
168 | 'Unstaking liquidity token failed!'
169 | );
170 | }
171 |
172 | function _unstakeRewards() private {
173 | uint256 rewards = rewardsPerAddress[msg.sender];
174 | allocatedStakingRewards = allocatedStakingRewards.sub(rewards);
175 | rewardsPerAddress[msg.sender] = 0;
176 | require(
177 | token.transfer(msg.sender, rewards),
178 | 'Unstaking rewards failed!'
179 | );
180 | }
181 |
182 | function unstake() external onlyReleasable nonReentrant returns(bool) {
183 | _unstakeLiquidity();
184 | _unstakeRewards();
185 | return true;
186 | }
187 |
188 | function enableUnstakeEarly() external onlyOwner returns(bool) {
189 | unstakeEarlyAllowed = true;
190 | return true;
191 | }
192 |
193 | function unstakeEarly() external onlyUnstakeEarly nonReentrant returns(bool) {
194 | _unstakeLiquidity();
195 | allocatedStakingRewards = allocatedStakingRewards.sub(rewardsPerAddress[msg.sender]);
196 | rewardsPerAddress[msg.sender] = 0;
197 | return true;
198 | }
199 |
200 | function isContract(address account) internal view returns(bool) {
201 | // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
202 | // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
203 | // for accounts without code, i.e. `keccak256('')`
204 | bytes32 codehash;
205 | bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
206 | // solhint-disable-next-line no-inline-assembly
207 | assembly { codehash := extcodehash(account) }
208 | return (codehash != accountHash && codehash != 0x0);
209 | }
210 |
211 | function destroy() public onlyOwner onlyDistributedRewards returns(bool) {
212 | selfdestruct(payable(owner()));
213 | return true;
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/docs/audit3.md:
--------------------------------------------------------------------------------
1 | # Audit report
2 |
3 | | Name | Information |
4 | |:----------:|-----------|
5 | | Repository | https://github.com/DePayFi/ethereum-staking |
6 | | Checked | [ ed516563f860440546f0f2967621ff9fe66f3972](https://github.com/DePayFi/ethereum-staking/blob/master/contracts/DePayLiquidityStaking.sol) |
7 | | Branch | [master](https://github.com/DePayFi/ethereum-staking) |
8 | | Time | Thu, 13 Dec 2020 03:59:35 UTC |
9 | | Author | Temitayo Daniel|
10 |
11 | # Result
12 |
13 | | Severity | Count | Link |
14 | |:--------:|----------:|------|
15 | | High | 1 | |
16 | |||[H01 - reward can be increased based on liquidity ](#H01)|
17 | | Medium | 0 | |
18 | | Low | 4 | |
19 | |||[L01 - Unnecessary override modifiers](#L01)|
20 | |||[L02 - block.timestamp/now could be manipulated by miner to a certain degree](#L02)|
21 | |||[L03 - It's better to returns(bool) for most functions-- double recommend from first audit](#L03)|
22 | |||[L04 - Consider adding virtual keyword to function)](#L04)|
23 |
24 |
25 |
26 |
27 | ## H01 - reward can be increased based on liquidity
28 |
29 | | Affected | Severity | Count | Lines |
30 | |:---------------:|:----------|------:|-------:|
31 | | DePayLiquidityStaking.sol | High | 1 | [124-128](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L124)|
32 |
33 | According to line 124, rewards for a user are calculated based on the reward token reserve `reserve0`, checking [UniswapV2Pair.sol](https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2Pair.sol#L185)| , the `reserve0` is updated based on the arguments entered for the swap function here
34 |
35 | ```solidity
36 | function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data)
37 | ````
38 | and finally updated here
39 |
40 | ```solidity
41 | _update(balance0, balance1, _reserve0, _reserve1);
42 | ```
43 |
44 | This means an attacker can deliberately increase the output of `reserve0` by abusing the external `swap` function on uniswap and go on to call `stake` on `DePayLiquidityStaking.sol`, thereby getting more rewards than intended.
45 |
46 | **Suggested Fix** Many protocols have fell prey to this simple bug by relying on data gotten from price oracles to calculate their own contract rewards. Unfortunately, the only solution to this dangerous severity is to change the mechanism defined [here] (https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L124) to something else which will not rely on `reserve0`
47 |
48 |
49 |
50 |
51 | ## L01 - Unnecessary override modifiers
52 |
53 | | Affected | Severity | Count | Lines |
54 | |:----------------|:----------|------:|-------:|
55 | | DePayLiquidityStaking.sol | Low | 13 |[20](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L20), [23](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L23), [26](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L26), [29](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L29), [32](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L32), [35](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L35), [38](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L38), [40](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L40), [48](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L48), [51](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L51), [177](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L177), [182](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L182), [186](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L186)|
56 |
57 | If there are no contracts that are inheriting `DePayLiquidityStaking.sol` then these variables,mappings and methods should not have the `override` modifier.
58 |
59 |
60 |
61 |
62 | ## L02 - block.timestamp/now could be manipulated by miner to a certain degree
63 |
64 | | Affected | Severity | Count | Lines |
65 | |:----------------|:----------|------:|-------:|
66 | | DePayLiquidityStaking.sol | Low | 4 |
67 | [55](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L55), [62](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L62), [67](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L67), [72](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L72)|
68 | | uniswap_v2_pair.sol | Low | 1 |
69 | [23](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/uniswap_v2_pair.sol#L23)|
70 |
71 | `block.timestamp` could be manipulated by miner up to a particular degree, It might not match current time. but can be used in this case. Do not use block.timestamp for calculation in randomness strategies
72 |
73 |
74 | It's better to returns(bool
75 |
76 | ## L03 - It's better to returns(bool) for most functions-- double recommend from first audit
77 |
78 | | Affected | Severity | Count | Lines |
79 | |:----------------|:----------|------:|-------:|
80 | | DePayLiquidityStaking.sol | Low | 5 |[93](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L93), [106]https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L106), [141](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L141), [177](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L177), [182](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L182)|
81 |
82 | `return true;` or the corresponding boolean value will help in cross-contract interaction especially for precise decision making. Always return success values after calling functions.
83 |
84 | E.g: Call `unstakeEarly()` from another `contract` address.
85 |
86 | ```solidity
87 | function unstakeEarly() override external onlyUnstakeEarly nonReentrant {
88 | _unstakeLiquidity();
89 | allocatedStakingRewards = allocatedStakingRewards.sub(rewardsPerAddress[msg.sender]);
90 | rewardsPerAddress[msg.sender] = 0;
91 | }
92 | ```
93 |
94 | **Suggested fix:**
95 |
96 | ```solidityConsider adding virtual modifier to function
97 | function unstakeEarly() override external onlyUnstakeEarly nonReentrant returns(true) {
98 | _unstakeLiquidity();
99 | allocatedStakingRewards = allocatedStakingRewards.sub(rewardsPerAddress[msg.sender]);
100 | rewardsPerAddress[msg.sender] = 0;
101 | }
102 | }
103 | ```
104 | **so in your personal contract, you can have**
105 |
106 | ```solidity
107 | function doStuff() {
108 | require(DepayLiquidityStaking.unstakeEarly()==true);
109 | //do stuff
110 |
111 | }
112 | ```
113 |
114 |
115 | ## L04 - Consider adding virtual keyword to function
116 |
117 | | Affected | Severity | Count | Lines |
118 | |:----------------|:----------|------:|-------:|
119 | | DePayLiquidityStaking.sol | Low | 1 |[93](https://github.com/DePayFi/ethereum-staking/blob/ed516563f860440546f0f2967621ff9fe66f3972/contracts/DePayLiquidityStaking.sol#L93)|
120 |
121 | the `init` function is a very common function name and will be used across contract implementations so it is necessary to add the `virtual` keyword to override.
122 |
123 |
--------------------------------------------------------------------------------
/docs/audit1.md:
--------------------------------------------------------------------------------
1 | # Audit report
2 |
3 | | Name | Information |
4 | |:----------:|-----------|
5 | | Repository | https://github.com/DePayFi/ethereum-staking |
6 | | Revision | [72b189ff773993ed70efcb84992e48ca76b2ca7d](https://github.com/DePayFi/ethereum-staking/tree/72b189ff773993ed70efcb84992e48ca76b2ca7d) |
7 | | Branch | [master](https://github.com/DePayFi/ethereum-staking) |
8 | | Time | Thu, 10 Dec 2020 03:59:35 UTC |
9 | | Author | Chiro Hiro |
10 |
11 | # Result
12 |
13 | | Severity | Count | Link |
14 | |:--------:|----------:|------|
15 | | High | 2 | |
16 | |||[H01 - Possible fund lost and trapped](#H01)|
17 | |||[H02 - Year 2038 problem](#H02)|
18 | | Medium | 3 | |
19 | |||[M01 - Possible wrong modifier implement](#M01)|
20 | |||[M02 - Missing check for close time](#M02)|
21 | |||[M03 - All variables were not consumed](#M03)|
22 | | Low | 8 | |
23 | |||[L01 - It's better to have destroy method](#L01)|
24 | |||[L02 - It's better to have fixed range of version](#L02)|
25 | |||[L03 - Unnecessary override modifier](#L03)|
26 | |||[L04 - Unused property](#L04)|
27 | |||[L05 - block.timestamp could be manipulated by miner](#L05)|
28 | |||[L06 - Unnecessary temporary variable](#L06)|
29 | |||[L07 - It's better to returns(bool)](#L07)|
30 | |||[L08 - Complex type cast](#L08)|
31 |
32 |
33 |
34 | ## H01 - Possible fund lost and trapped
35 |
36 | | Affected | Severity | Count | Lines |
37 | |:---------------:|:----------|------:|-------:|
38 | | DePayLiquidityStaking.sol | High | 2 | [148](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L148), [165](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L165)|
39 |
40 | It could be happened in the same way of fake deposit attack where your `call()` is failed but in the contract's state were updated.
41 |
42 | ```solidity
43 | function withdraw(
44 | address tokenAddress,
45 | uint amount
46 | ) override external onlyOwner nonReentrant {
47 | require(tokenAddress != address(liquidityToken), 'Not allowed to withdrawal liquidity tokens!');
48 |
49 | if(tokenAddress == address(token)) {
50 | require(
51 | allocatedStakingRewards <= token.balanceOf(address(this)).sub(amount),
52 | 'Only unallocated staking rewards are allowed to be withdrawn for roll-over to next staking contract!'
53 | );
54 | rewardsAmount = rewardsAmount.sub(amount);
55 | }
56 |
57 | IERC20(tokenAddress).transfer(payableOwner(), amount);
58 | }
59 | ```
60 |
61 | `rewardsAmount = rewardsAmount.sub(amount);` possible to be happened even `IERC20(tokenAddress).transfer(payableOwner(), amount);` was failed.
62 |
63 | **Suggest Fix:** Please use [SafeERC20](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/SafeERC20.sol)
64 |
65 | ```solidity
66 | using SafeERC20 for ERC20
67 |
68 | function withdraw(
69 | address tokenAddress,
70 | uint amount
71 | ) override external onlyOwner nonReentrant {
72 | require(tokenAddress != address(liquidityToken), 'Not allowed to withdrawal liquidity tokens!');
73 |
74 | if(tokenAddress == address(token)) {
75 | require(
76 | allocatedStakingRewards <= token.balanceOf(address(this)).sub(amount),
77 | 'Only unallocated staking rewards are allowed to be withdrawn for roll-over to next staking contract!'
78 | );
79 | rewardsAmount = rewardsAmount.sub(amount);
80 | }
81 |
82 | IERC20(tokenAddress).safeTransfer(payableOwner(), amount);
83 | }
84 | ```
85 |
86 |
87 |
88 | ## H02 - Year 2038 problem
89 |
90 | | Affected | Severity | Count | Lines |
91 | |:----------------|:----------|------:|-------:|
92 | | uniswap_v2_pair.sol | High | 1 |[20](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/uniswap_v2_pair.sol#L20)|
93 |
94 | Unix timestamp will be overflow at 2038 since in many system it was **unsigned integer 32 bits**. Apparently, `0xffffffff + 1 = 0x100000000` but It will be truncated to `0x00000000` to fit `uint32`. we didn't use this code any where but please aware of this.
95 |
96 |
97 |
98 | ## M01 - Possible wrong modifier implement
99 |
100 | | Affected | Severity | Count | Lines |
101 | |:-------------:|:----------|------:|-------:|
102 | | DePayLiquidityStaking.sol | Medium | 1 | [26-27](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L54-L60)
103 |
104 | ```solidity
105 | modifier onlyUnstarted() {
106 | require(
107 | startTime == 0 || block.timestamp < startTime,
108 | "Staking has already started!"
109 | );
110 | _;
111 | }
112 | ```
113 |
114 | `block.timestamp < startTime` This condition is always `true` since `startTime = 0`, It's only make sense if you want to call `init()` more than once(?).
115 |
116 |
117 |
118 | ## M02 - Missing check for close time
119 |
120 | | Affected | Severity | Count | Lines |
121 | |:----------------|:----------|------:|-------:|
122 | | DePayLiquidityStaking.sol | Medium | 1 | [83-84](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L83-L84)|
123 |
124 | This one is just a logic issue, you might need to have some addition checks.
125 |
126 | E.g: If you set `_startTime = block.timestamp` and `_closeTime < _startTime` then contract would become unusable.
127 |
128 |
129 |
130 |
131 | ## M03 - Some variables were not consumed
132 |
133 | | Affected | Severity | Count | Lines |
134 | |:----------------|:----------|------:|-------:|
135 | | DePayLiquidityStaking.sol | Medium | 1 |[107-110](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L107-L110)|
136 |
137 | ```solidity
138 | uint112 reserve0;
139 | uint112 reserve1;
140 | uint32 blockTimestampLast;
141 | (reserve0, reserve1, blockTimestampLast) = liquidityToken.getReserves();
142 | ```
143 |
144 | `reserve1` and `blockTimestampLast` were not consumed.
145 |
146 | **Suggest Fix:**
147 |
148 | [uniswap_v2_pair.sol#L9-L11](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/uniswap_v2_pair.sol#L9-L11)
149 |
150 | ```solidity
151 | uint112 public reserve0;
152 | uint112 public reserve1;
153 | ```
154 |
155 | [DePayLiquidityStaking.sol#L107-L110](https://github.com/DePayFi/ethereum-staking/blob/2ceeb6c6b3d428f1689ece4927d2222ba74eae90/contracts/DePayLiquidityStaking.sol#L107-L110)
156 |
157 | ```solidity
158 | uint112 reserve0 = liquidityToken.reserve0();
159 | ```
160 |
161 |
162 | ## L01 - It's better to have destroy method
163 |
164 | I think `DePayLiquidityStaking` contract is only usable for a duration, please consider to have `destroy()` method to get back your ETH.
165 |
166 | ```solidity
167 | modifier onlyDistributedReward(){
168 | require(allocatedStakingRewards == 0, 'Rewards were not distributed');
169 | _;
170 | }
171 |
172 | modifier onlyClosed() {
173 | require(block.timestamp > closeTime, "Staking still opening!");
174 | _;
175 | }
176 |
177 | function destroy() onlyOwner onlyClosed onlyDistributedReward {
178 | selfdestruct(payable(owner()));
179 | }
180 | ```
181 |
182 |
183 |
184 | ## L02 - It's better to have fixed range of version
185 |
186 | | Affected | Severity | Count | Lines |
187 | |:----------------|:----------|------:|-------:|
188 | | DePayLiquidityStaking.sol | Low | 1 |[3](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L3)|
189 | | uniswap_v2_pair.sol | Low | 1 |[3](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/uniswap_v2_pair.sol#L3)|
190 | | token.sol | Low | 1 |[3](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/token.sol#L3)|
191 | | IDePayLiquidityStaking.sol | Low | 1 |[3](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/interfaces/IDePayLiquidityStaking.sol#L3)|
192 | | IUniswapV2Pair.sol | Low | 1 |[3](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/interfaces/IUniswapV2Pair.sol#L3)|
193 |
194 |
195 | `0.8.x` would have some new breaking changes, please consider to change this:
196 |
197 | ```solidity
198 | pragma solidity >= 0.7.5;
199 | ```
200 |
201 | To this:
202 | ```solidity
203 | pragma solidity >=0.7.5 <0.8.0;
204 | ```
205 |
206 |
207 |
208 | ## L03 - Unnecessary override modifier
209 |
210 | | Affected | Severity | Count | Lines |
211 | |:----------------|:----------|------:|-------:|
212 | | DePayLiquidityStaking.sol | Low | 16 |[18](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L18), [21](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L21), [24](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L24), [27](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L27), [30](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L30), [33](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L33), [36](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L36), [39](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L39), [43.46](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L43.46), [49](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L49), [89](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L89), [101](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L101), [137](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L137), [170](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L170), [175](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L175), [179](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L179)|
213 |
214 | If there are no contracts derive from `DePayLiquidityStaking.sol` then these properties and methods shouldn't be `override`.
215 |
216 |
217 |
218 | ## L04 - Unused property
219 |
220 | | Affected | Severity | Count | Lines |
221 | |:----------------|:----------|------:|-------:|
222 | | DePayLiquidityStaking.sol | Low | 1 |[52](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L52)|
223 |
224 | ```solidity
225 | // Address ZERO
226 | address private ZERO = 0x0000000000000000000000000000000000000000;
227 | ```
228 |
229 | This property wasn't used anywhere, you might need to use `address(0)` instead.
230 |
231 |
232 |
233 | ## L05 - block.timestamp could be manipulated by miner
234 |
235 | | Affected | Severity | Count | Lines |
236 | |:----------------|:----------|------:|-------:|
237 | | DePayLiquidityStaking.sol | Low | 4 |
238 | [56](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L56), [63](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L63), [68](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L68), [73](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L73)|
239 | | uniswap_v2_pair.sol | Low | 1 |
240 | [23](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/uniswap_v2_pair.sol#L23)|
241 |
242 | `block.timestamp` could be manipulated by miner, It might not match current time.
243 |
244 |
245 |
246 | ## L06 - Unnecessary temporary variable
247 |
248 | | Affected | Severity | Count | Lines |
249 | |:----------------|:----------|------:|-------:|
250 | | DePayLiquidityStaking.sol | Low | 1 |
251 | [179-184](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L179-L184)|
252 |
253 | ```solidity
254 | function unstakeEarly() override external onlyUnstakeEarly nonReentrant {
255 | _unstakeLiquidity();
256 | uint256 rewards = rewardsPerAddress[msg.sender];
257 | allocatedStakingRewards = allocatedStakingRewards.sub(rewards);
258 | rewardsPerAddress[msg.sender] = 0;
259 | }
260 | ```
261 |
262 | The code from above cost more than `22 gas`
263 |
264 | ```solidity
265 | function unstakeEarly() override external onlyUnstakeEarly nonReentrant {
266 | _unstakeLiquidity();
267 | allocatedStakingRewards = allocatedStakingRewards.sub(rewardsPerAddress[msg.sender]);
268 | rewardsPerAddress[msg.sender] = 0;
269 | }
270 | ```
271 |
272 |
273 |
274 | ## L07 - It's better to returns(bool)
275 |
276 | | Affected | Severity | Count | Lines |
277 | |:----------------|:----------|------:|-------:|
278 | | DePayLiquidityStaking.sol | Low | 6 |[89](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L89), [101](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L101), [137](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L137), [170](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L170), [175](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L175), [179](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L179)|
279 |
280 | `return true;` at the end of your function will help interactive between other contract be more secure.
281 |
282 | E.g: Call `init()` from your `multi-sig` wallet.
283 |
284 | ```solidity
285 | function init(
286 | uint256 _startTime,
287 | uint256 _closeTime,
288 | uint256 _releaseTime,
289 | uint256 _percentageYield,
290 | address _liquidityToken,
291 | address _token
292 | ) override external onlyOwner onlyUnstarted {
293 | startTime = _startTime;
294 | closeTime = _closeTime;
295 | releaseTime = _releaseTime;
296 | percentageYield = _percentageYield;
297 | liquidityToken = IUniswapV2Pair(_liquidityToken);
298 | token = IERC20(_token);
299 | rewardsAmount = token.balanceOf(address(this));
300 | }
301 | ```
302 |
303 | **Suggest fix:**
304 |
305 | ```solidity
306 | function init(
307 | uint256 _startTime,
308 | uint256 _closeTime,
309 | uint256 _releaseTime,
310 | uint256 _percentageYield,
311 | address _liquidityToken,
312 | address _token
313 | ) override external onlyOwner onlyUnstarted returns(bool) {
314 | startTime = _startTime;
315 | closeTime = _closeTime;
316 | releaseTime = _releaseTime;
317 | percentageYield = _percentageYield;
318 | liquidityToken = IUniswapV2Pair(_liquidityToken);
319 | token = IERC20(_token);
320 | rewardsAmount = token.balanceOf(address(this));
321 | return true;
322 | }
323 | ```
324 |
325 |
326 |
327 | ## L08 - Complex type cast
328 |
329 | | Affected | Severity | Count | Lines |
330 | |:----------------|:----------|------:|-------:|
331 | | DePayLiquidityStaking.sol | Low | 1 |[30-32](https://github.com/DePayFi/ethereum-staking/blob/72b189ff773993ed70efcb84992e48ca76b2ca7d/contracts/DePayLiquidityStaking.sol#L130-L132)|
332 |
333 |
334 | ```solidity
335 | function payableOwner() view private returns(address payable) {
336 | return address(uint160(owner()));
337 | }
338 | ```
339 |
340 | **Suggest fix:**
341 |
342 | ```solidity
343 | function payableOwner() view private returns(address payable) {
344 | return payable(owner());
345 | }
346 | ```
347 |
--------------------------------------------------------------------------------
/flatten/token.sol:
--------------------------------------------------------------------------------
1 | // Dependency file: @openzeppelin/contracts/GSN/Context.sol
2 |
3 | // SPDX-License-Identifier: MIT
4 |
5 | // pragma solidity >=0.6.0 <0.8.0;
6 |
7 | /*
8 | * @dev Provides information about the current execution context, including the
9 | * sender of the transaction and its data. While these are generally available
10 | * via msg.sender and msg.data, they should not be accessed in such a direct
11 | * manner, since when dealing with GSN meta-transactions the account sending and
12 | * paying for execution may not be the actual sender (as far as an application
13 | * is concerned).
14 | *
15 | * This contract is only required for intermediate, library-like contracts.
16 | */
17 | abstract contract Context {
18 | function _msgSender() internal view virtual returns (address payable) {
19 | return msg.sender;
20 | }
21 |
22 | function _msgData() internal view virtual returns (bytes memory) {
23 | this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
24 | return msg.data;
25 | }
26 | }
27 |
28 |
29 | // Dependency file: @openzeppelin/contracts/token/ERC20/IERC20.sol
30 |
31 |
32 | // pragma solidity >=0.6.0 <0.8.0;
33 |
34 | /**
35 | * @dev Interface of the ERC20 standard as defined in the EIP.
36 | */
37 | interface IERC20 {
38 | /**
39 | * @dev Returns the amount of tokens in existence.
40 | */
41 | function totalSupply() external view returns (uint256);
42 |
43 | /**
44 | * @dev Returns the amount of tokens owned by `account`.
45 | */
46 | function balanceOf(address account) external view returns (uint256);
47 |
48 | /**
49 | * @dev Moves `amount` tokens from the caller's account to `recipient`.
50 | *
51 | * Returns a boolean value indicating whether the operation succeeded.
52 | *
53 | * Emits a {Transfer} event.
54 | */
55 | function transfer(address recipient, uint256 amount) external returns (bool);
56 |
57 | /**
58 | * @dev Returns the remaining number of tokens that `spender` will be
59 | * allowed to spend on behalf of `owner` through {transferFrom}. This is
60 | * zero by default.
61 | *
62 | * This value changes when {approve} or {transferFrom} are called.
63 | */
64 | function allowance(address owner, address spender) external view returns (uint256);
65 |
66 | /**
67 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
68 | *
69 | * Returns a boolean value indicating whether the operation succeeded.
70 | *
71 | * // importANT: Beware that changing an allowance with this method brings the risk
72 | * that someone may use both the old and the new allowance by unfortunate
73 | * transaction ordering. One possible solution to mitigate this race
74 | * condition is to first reduce the spender's allowance to 0 and set the
75 | * desired value afterwards:
76 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
77 | *
78 | * Emits an {Approval} event.
79 | */
80 | function approve(address spender, uint256 amount) external returns (bool);
81 |
82 | /**
83 | * @dev Moves `amount` tokens from `sender` to `recipient` using the
84 | * allowance mechanism. `amount` is then deducted from the caller's
85 | * allowance.
86 | *
87 | * Returns a boolean value indicating whether the operation succeeded.
88 | *
89 | * Emits a {Transfer} event.
90 | */
91 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
92 |
93 | /**
94 | * @dev Emitted when `value` tokens are moved from one account (`from`) to
95 | * another (`to`).
96 | *
97 | * Note that `value` may be zero.
98 | */
99 | event Transfer(address indexed from, address indexed to, uint256 value);
100 |
101 | /**
102 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by
103 | * a call to {approve}. `value` is the new allowance.
104 | */
105 | event Approval(address indexed owner, address indexed spender, uint256 value);
106 | }
107 |
108 |
109 | // Dependency file: @openzeppelin/contracts/math/SafeMath.sol
110 |
111 |
112 | // pragma solidity >=0.6.0 <0.8.0;
113 |
114 | /**
115 | * @dev Wrappers over Solidity's arithmetic operations with added overflow
116 | * checks.
117 | *
118 | * Arithmetic operations in Solidity wrap on overflow. This can easily result
119 | * in bugs, because programmers usually assume that an overflow raises an
120 | * error, which is the standard behavior in high level programming languages.
121 | * `SafeMath` restores this intuition by reverting the transaction when an
122 | * operation overflows.
123 | *
124 | * Using this library instead of the unchecked operations eliminates an entire
125 | * class of bugs, so it's recommended to use it always.
126 | */
127 | library SafeMath {
128 | /**
129 | * @dev Returns the addition of two unsigned integers, reverting on
130 | * overflow.
131 | *
132 | * Counterpart to Solidity's `+` operator.
133 | *
134 | * Requirements:
135 | *
136 | * - Addition cannot overflow.
137 | */
138 | function add(uint256 a, uint256 b) internal pure returns (uint256) {
139 | uint256 c = a + b;
140 | require(c >= a, "SafeMath: addition overflow");
141 |
142 | return c;
143 | }
144 |
145 | /**
146 | * @dev Returns the subtraction of two unsigned integers, reverting on
147 | * overflow (when the result is negative).
148 | *
149 | * Counterpart to Solidity's `-` operator.
150 | *
151 | * Requirements:
152 | *
153 | * - Subtraction cannot overflow.
154 | */
155 | function sub(uint256 a, uint256 b) internal pure returns (uint256) {
156 | return sub(a, b, "SafeMath: subtraction overflow");
157 | }
158 |
159 | /**
160 | * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
161 | * overflow (when the result is negative).
162 | *
163 | * Counterpart to Solidity's `-` operator.
164 | *
165 | * Requirements:
166 | *
167 | * - Subtraction cannot overflow.
168 | */
169 | function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
170 | require(b <= a, errorMessage);
171 | uint256 c = a - b;
172 |
173 | return c;
174 | }
175 |
176 | /**
177 | * @dev Returns the multiplication of two unsigned integers, reverting on
178 | * overflow.
179 | *
180 | * Counterpart to Solidity's `*` operator.
181 | *
182 | * Requirements:
183 | *
184 | * - Multiplication cannot overflow.
185 | */
186 | function mul(uint256 a, uint256 b) internal pure returns (uint256) {
187 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
188 | // benefit is lost if 'b' is also tested.
189 | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
190 | if (a == 0) {
191 | return 0;
192 | }
193 |
194 | uint256 c = a * b;
195 | require(c / a == b, "SafeMath: multiplication overflow");
196 |
197 | return c;
198 | }
199 |
200 | /**
201 | * @dev Returns the integer division of two unsigned integers. Reverts on
202 | * division by zero. The result is rounded towards zero.
203 | *
204 | * Counterpart to Solidity's `/` operator. Note: this function uses a
205 | * `revert` opcode (which leaves remaining gas untouched) while Solidity
206 | * uses an invalid opcode to revert (consuming all remaining gas).
207 | *
208 | * Requirements:
209 | *
210 | * - The divisor cannot be zero.
211 | */
212 | function div(uint256 a, uint256 b) internal pure returns (uint256) {
213 | return div(a, b, "SafeMath: division by zero");
214 | }
215 |
216 | /**
217 | * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
218 | * division by zero. The result is rounded towards zero.
219 | *
220 | * Counterpart to Solidity's `/` operator. Note: this function uses a
221 | * `revert` opcode (which leaves remaining gas untouched) while Solidity
222 | * uses an invalid opcode to revert (consuming all remaining gas).
223 | *
224 | * Requirements:
225 | *
226 | * - The divisor cannot be zero.
227 | */
228 | function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
229 | require(b > 0, errorMessage);
230 | uint256 c = a / b;
231 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold
232 |
233 | return c;
234 | }
235 |
236 | /**
237 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
238 | * Reverts when dividing by zero.
239 | *
240 | * Counterpart to Solidity's `%` operator. This function uses a `revert`
241 | * opcode (which leaves remaining gas untouched) while Solidity uses an
242 | * invalid opcode to revert (consuming all remaining gas).
243 | *
244 | * Requirements:
245 | *
246 | * - The divisor cannot be zero.
247 | */
248 | function mod(uint256 a, uint256 b) internal pure returns (uint256) {
249 | return mod(a, b, "SafeMath: modulo by zero");
250 | }
251 |
252 | /**
253 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
254 | * Reverts with custom message when dividing by zero.
255 | *
256 | * Counterpart to Solidity's `%` operator. This function uses a `revert`
257 | * opcode (which leaves remaining gas untouched) while Solidity uses an
258 | * invalid opcode to revert (consuming all remaining gas).
259 | *
260 | * Requirements:
261 | *
262 | * - The divisor cannot be zero.
263 | */
264 | function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
265 | require(b != 0, errorMessage);
266 | return a % b;
267 | }
268 | }
269 |
270 |
271 | // Dependency file: @openzeppelin/contracts/token/ERC20/ERC20.sol
272 |
273 |
274 | // pragma solidity >=0.6.0 <0.8.0;
275 |
276 | // import "@openzeppelin/contracts/GSN/Context.sol";
277 | // import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
278 | // import "@openzeppelin/contracts/math/SafeMath.sol";
279 |
280 | /**
281 | * @dev Implementation of the {IERC20} interface.
282 | *
283 | * This implementation is agnostic to the way tokens are created. This means
284 | * that a supply mechanism has to be added in a derived contract using {_mint}.
285 | * For a generic mechanism see {ERC20PresetMinterPauser}.
286 | *
287 | * TIP: For a detailed writeup see our guide
288 | * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
289 | * to implement supply mechanisms].
290 | *
291 | * We have followed general OpenZeppelin guidelines: functions revert instead
292 | * of returning `false` on failure. This behavior is nonetheless conventional
293 | * and does not conflict with the expectations of ERC20 applications.
294 | *
295 | * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
296 | * This allows applications to reconstruct the allowance for all accounts just
297 | * by listening to said events. Other implementations of the EIP may not emit
298 | * these events, as it isn't required by the specification.
299 | *
300 | * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
301 | * functions have been added to mitigate the well-known issues around setting
302 | * allowances. See {IERC20-approve}.
303 | */
304 | contract ERC20 is Context, IERC20 {
305 | using SafeMath for uint256;
306 |
307 | mapping (address => uint256) private _balances;
308 |
309 | mapping (address => mapping (address => uint256)) private _allowances;
310 |
311 | uint256 private _totalSupply;
312 |
313 | string private _name;
314 | string private _symbol;
315 | uint8 private _decimals;
316 |
317 | /**
318 | * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
319 | * a default value of 18.
320 | *
321 | * To select a different value for {decimals}, use {_setupDecimals}.
322 | *
323 | * All three of these values are immutable: they can only be set once during
324 | * construction.
325 | */
326 | constructor (string memory name_, string memory symbol_) public {
327 | _name = name_;
328 | _symbol = symbol_;
329 | _decimals = 18;
330 | }
331 |
332 | /**
333 | * @dev Returns the name of the token.
334 | */
335 | function name() public view returns (string memory) {
336 | return _name;
337 | }
338 |
339 | /**
340 | * @dev Returns the symbol of the token, usually a shorter version of the
341 | * name.
342 | */
343 | function symbol() public view returns (string memory) {
344 | return _symbol;
345 | }
346 |
347 | /**
348 | * @dev Returns the number of decimals used to get its user representation.
349 | * For example, if `decimals` equals `2`, a balance of `505` tokens should
350 | * be displayed to a user as `5,05` (`505 / 10 ** 2`).
351 | *
352 | * Tokens usually opt for a value of 18, imitating the relationship between
353 | * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
354 | * called.
355 | *
356 | * NOTE: This information is only used for _display_ purposes: it in
357 | * no way affects any of the arithmetic of the contract, including
358 | * {IERC20-balanceOf} and {IERC20-transfer}.
359 | */
360 | function decimals() public view returns (uint8) {
361 | return _decimals;
362 | }
363 |
364 | /**
365 | * @dev See {IERC20-totalSupply}.
366 | */
367 | function totalSupply() public view override returns (uint256) {
368 | return _totalSupply;
369 | }
370 |
371 | /**
372 | * @dev See {IERC20-balanceOf}.
373 | */
374 | function balanceOf(address account) public view override returns (uint256) {
375 | return _balances[account];
376 | }
377 |
378 | /**
379 | * @dev See {IERC20-transfer}.
380 | *
381 | * Requirements:
382 | *
383 | * - `recipient` cannot be the zero address.
384 | * - the caller must have a balance of at least `amount`.
385 | */
386 | function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
387 | _transfer(_msgSender(), recipient, amount);
388 | return true;
389 | }
390 |
391 | /**
392 | * @dev See {IERC20-allowance}.
393 | */
394 | function allowance(address owner, address spender) public view virtual override returns (uint256) {
395 | return _allowances[owner][spender];
396 | }
397 |
398 | /**
399 | * @dev See {IERC20-approve}.
400 | *
401 | * Requirements:
402 | *
403 | * - `spender` cannot be the zero address.
404 | */
405 | function approve(address spender, uint256 amount) public virtual override returns (bool) {
406 | _approve(_msgSender(), spender, amount);
407 | return true;
408 | }
409 |
410 | /**
411 | * @dev See {IERC20-transferFrom}.
412 | *
413 | * Emits an {Approval} event indicating the updated allowance. This is not
414 | * required by the EIP. See the note at the beginning of {ERC20}.
415 | *
416 | * Requirements:
417 | *
418 | * - `sender` and `recipient` cannot be the zero address.
419 | * - `sender` must have a balance of at least `amount`.
420 | * - the caller must have allowance for ``sender``'s tokens of at least
421 | * `amount`.
422 | */
423 | function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
424 | _transfer(sender, recipient, amount);
425 | _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
426 | return true;
427 | }
428 |
429 | /**
430 | * @dev Atomically increases the allowance granted to `spender` by the caller.
431 | *
432 | * This is an alternative to {approve} that can be used as a mitigation for
433 | * problems described in {IERC20-approve}.
434 | *
435 | * Emits an {Approval} event indicating the updated allowance.
436 | *
437 | * Requirements:
438 | *
439 | * - `spender` cannot be the zero address.
440 | */
441 | function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
442 | _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
443 | return true;
444 | }
445 |
446 | /**
447 | * @dev Atomically decreases the allowance granted to `spender` by the caller.
448 | *
449 | * This is an alternative to {approve} that can be used as a mitigation for
450 | * problems described in {IERC20-approve}.
451 | *
452 | * Emits an {Approval} event indicating the updated allowance.
453 | *
454 | * Requirements:
455 | *
456 | * - `spender` cannot be the zero address.
457 | * - `spender` must have allowance for the caller of at least
458 | * `subtractedValue`.
459 | */
460 | function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
461 | _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
462 | return true;
463 | }
464 |
465 | /**
466 | * @dev Moves tokens `amount` from `sender` to `recipient`.
467 | *
468 | * This is internal function is equivalent to {transfer}, and can be used to
469 | * e.g. implement automatic token fees, slashing mechanisms, etc.
470 | *
471 | * Emits a {Transfer} event.
472 | *
473 | * Requirements:
474 | *
475 | * - `sender` cannot be the zero address.
476 | * - `recipient` cannot be the zero address.
477 | * - `sender` must have a balance of at least `amount`.
478 | */
479 | function _transfer(address sender, address recipient, uint256 amount) internal virtual {
480 | require(sender != address(0), "ERC20: transfer from the zero address");
481 | require(recipient != address(0), "ERC20: transfer to the zero address");
482 |
483 | _beforeTokenTransfer(sender, recipient, amount);
484 |
485 | _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
486 | _balances[recipient] = _balances[recipient].add(amount);
487 | emit Transfer(sender, recipient, amount);
488 | }
489 |
490 | /** @dev Creates `amount` tokens and assigns them to `account`, increasing
491 | * the total supply.
492 | *
493 | * Emits a {Transfer} event with `from` set to the zero address.
494 | *
495 | * Requirements:
496 | *
497 | * - `to` cannot be the zero address.
498 | */
499 | function _mint(address account, uint256 amount) internal virtual {
500 | require(account != address(0), "ERC20: mint to the zero address");
501 |
502 | _beforeTokenTransfer(address(0), account, amount);
503 |
504 | _totalSupply = _totalSupply.add(amount);
505 | _balances[account] = _balances[account].add(amount);
506 | emit Transfer(address(0), account, amount);
507 | }
508 |
509 | /**
510 | * @dev Destroys `amount` tokens from `account`, reducing the
511 | * total supply.
512 | *
513 | * Emits a {Transfer} event with `to` set to the zero address.
514 | *
515 | * Requirements:
516 | *
517 | * - `account` cannot be the zero address.
518 | * - `account` must have at least `amount` tokens.
519 | */
520 | function _burn(address account, uint256 amount) internal virtual {
521 | require(account != address(0), "ERC20: burn from the zero address");
522 |
523 | _beforeTokenTransfer(account, address(0), amount);
524 |
525 | _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
526 | _totalSupply = _totalSupply.sub(amount);
527 | emit Transfer(account, address(0), amount);
528 | }
529 |
530 | /**
531 | * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
532 | *
533 | * This internal function is equivalent to `approve`, and can be used to
534 | * e.g. set automatic allowances for certain subsystems, etc.
535 | *
536 | * Emits an {Approval} event.
537 | *
538 | * Requirements:
539 | *
540 | * - `owner` cannot be the zero address.
541 | * - `spender` cannot be the zero address.
542 | */
543 | function _approve(address owner, address spender, uint256 amount) internal virtual {
544 | require(owner != address(0), "ERC20: approve from the zero address");
545 | require(spender != address(0), "ERC20: approve to the zero address");
546 |
547 | _allowances[owner][spender] = amount;
548 | emit Approval(owner, spender, amount);
549 | }
550 |
551 | /**
552 | * @dev Sets {decimals} to a value other than the default one of 18.
553 | *
554 | * WARNING: This function should only be called from the constructor. Most
555 | * applications that interact with token contracts will not expect
556 | * {decimals} to ever change, and may work incorrectly if it does.
557 | */
558 | function _setupDecimals(uint8 decimals_) internal {
559 | _decimals = decimals_;
560 | }
561 |
562 | /**
563 | * @dev Hook that is called before any transfer of tokens. This includes
564 | * minting and burning.
565 | *
566 | * Calling conditions:
567 | *
568 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
569 | * will be to transferred to `to`.
570 | * - when `from` is zero, `amount` tokens will be minted for `to`.
571 | * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
572 | * - `from` and `to` are never both zero.
573 | *
574 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
575 | */
576 | function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
577 | }
578 |
579 |
580 | // Root file: contracts/test/token.sol
581 |
582 |
583 | // used for running automated hardhat tests
584 |
585 | pragma solidity >=0.7.5 <0.8.0;
586 |
587 | // import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
588 |
589 | contract Token is ERC20 {
590 |
591 | constructor() public ERC20("Token", "TKN") {
592 | _mint(msg.sender, 1000000000000000000000000);
593 | }
594 | }
595 |
--------------------------------------------------------------------------------
/flatten/token_safe_transfer.sol:
--------------------------------------------------------------------------------
1 | // Dependency file: @openzeppelin/contracts/GSN/Context.sol
2 |
3 | // SPDX-License-Identifier: MIT
4 |
5 | // pragma solidity >=0.6.0 <0.8.0;
6 |
7 | /*
8 | * @dev Provides information about the current execution context, including the
9 | * sender of the transaction and its data. While these are generally available
10 | * via msg.sender and msg.data, they should not be accessed in such a direct
11 | * manner, since when dealing with GSN meta-transactions the account sending and
12 | * paying for execution may not be the actual sender (as far as an application
13 | * is concerned).
14 | *
15 | * This contract is only required for intermediate, library-like contracts.
16 | */
17 | abstract contract Context {
18 | function _msgSender() internal view virtual returns (address payable) {
19 | return msg.sender;
20 | }
21 |
22 | function _msgData() internal view virtual returns (bytes memory) {
23 | this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
24 | return msg.data;
25 | }
26 | }
27 |
28 |
29 | // Dependency file: @openzeppelin/contracts/token/ERC20/IERC20.sol
30 |
31 |
32 | // pragma solidity >=0.6.0 <0.8.0;
33 |
34 | /**
35 | * @dev Interface of the ERC20 standard as defined in the EIP.
36 | */
37 | interface IERC20 {
38 | /**
39 | * @dev Returns the amount of tokens in existence.
40 | */
41 | function totalSupply() external view returns (uint256);
42 |
43 | /**
44 | * @dev Returns the amount of tokens owned by `account`.
45 | */
46 | function balanceOf(address account) external view returns (uint256);
47 |
48 | /**
49 | * @dev Moves `amount` tokens from the caller's account to `recipient`.
50 | *
51 | * Returns a boolean value indicating whether the operation succeeded.
52 | *
53 | * Emits a {Transfer} event.
54 | */
55 | function transfer(address recipient, uint256 amount) external returns (bool);
56 |
57 | /**
58 | * @dev Returns the remaining number of tokens that `spender` will be
59 | * allowed to spend on behalf of `owner` through {transferFrom}. This is
60 | * zero by default.
61 | *
62 | * This value changes when {approve} or {transferFrom} are called.
63 | */
64 | function allowance(address owner, address spender) external view returns (uint256);
65 |
66 | /**
67 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
68 | *
69 | * Returns a boolean value indicating whether the operation succeeded.
70 | *
71 | * // importANT: Beware that changing an allowance with this method brings the risk
72 | * that someone may use both the old and the new allowance by unfortunate
73 | * transaction ordering. One possible solution to mitigate this race
74 | * condition is to first reduce the spender's allowance to 0 and set the
75 | * desired value afterwards:
76 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
77 | *
78 | * Emits an {Approval} event.
79 | */
80 | function approve(address spender, uint256 amount) external returns (bool);
81 |
82 | /**
83 | * @dev Moves `amount` tokens from `sender` to `recipient` using the
84 | * allowance mechanism. `amount` is then deducted from the caller's
85 | * allowance.
86 | *
87 | * Returns a boolean value indicating whether the operation succeeded.
88 | *
89 | * Emits a {Transfer} event.
90 | */
91 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
92 |
93 | /**
94 | * @dev Emitted when `value` tokens are moved from one account (`from`) to
95 | * another (`to`).
96 | *
97 | * Note that `value` may be zero.
98 | */
99 | event Transfer(address indexed from, address indexed to, uint256 value);
100 |
101 | /**
102 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by
103 | * a call to {approve}. `value` is the new allowance.
104 | */
105 | event Approval(address indexed owner, address indexed spender, uint256 value);
106 | }
107 |
108 |
109 | // Dependency file: @openzeppelin/contracts/math/SafeMath.sol
110 |
111 |
112 | // pragma solidity >=0.6.0 <0.8.0;
113 |
114 | /**
115 | * @dev Wrappers over Solidity's arithmetic operations with added overflow
116 | * checks.
117 | *
118 | * Arithmetic operations in Solidity wrap on overflow. This can easily result
119 | * in bugs, because programmers usually assume that an overflow raises an
120 | * error, which is the standard behavior in high level programming languages.
121 | * `SafeMath` restores this intuition by reverting the transaction when an
122 | * operation overflows.
123 | *
124 | * Using this library instead of the unchecked operations eliminates an entire
125 | * class of bugs, so it's recommended to use it always.
126 | */
127 | library SafeMath {
128 | /**
129 | * @dev Returns the addition of two unsigned integers, reverting on
130 | * overflow.
131 | *
132 | * Counterpart to Solidity's `+` operator.
133 | *
134 | * Requirements:
135 | *
136 | * - Addition cannot overflow.
137 | */
138 | function add(uint256 a, uint256 b) internal pure returns (uint256) {
139 | uint256 c = a + b;
140 | require(c >= a, "SafeMath: addition overflow");
141 |
142 | return c;
143 | }
144 |
145 | /**
146 | * @dev Returns the subtraction of two unsigned integers, reverting on
147 | * overflow (when the result is negative).
148 | *
149 | * Counterpart to Solidity's `-` operator.
150 | *
151 | * Requirements:
152 | *
153 | * - Subtraction cannot overflow.
154 | */
155 | function sub(uint256 a, uint256 b) internal pure returns (uint256) {
156 | return sub(a, b, "SafeMath: subtraction overflow");
157 | }
158 |
159 | /**
160 | * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
161 | * overflow (when the result is negative).
162 | *
163 | * Counterpart to Solidity's `-` operator.
164 | *
165 | * Requirements:
166 | *
167 | * - Subtraction cannot overflow.
168 | */
169 | function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
170 | require(b <= a, errorMessage);
171 | uint256 c = a - b;
172 |
173 | return c;
174 | }
175 |
176 | /**
177 | * @dev Returns the multiplication of two unsigned integers, reverting on
178 | * overflow.
179 | *
180 | * Counterpart to Solidity's `*` operator.
181 | *
182 | * Requirements:
183 | *
184 | * - Multiplication cannot overflow.
185 | */
186 | function mul(uint256 a, uint256 b) internal pure returns (uint256) {
187 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
188 | // benefit is lost if 'b' is also tested.
189 | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
190 | if (a == 0) {
191 | return 0;
192 | }
193 |
194 | uint256 c = a * b;
195 | require(c / a == b, "SafeMath: multiplication overflow");
196 |
197 | return c;
198 | }
199 |
200 | /**
201 | * @dev Returns the integer division of two unsigned integers. Reverts on
202 | * division by zero. The result is rounded towards zero.
203 | *
204 | * Counterpart to Solidity's `/` operator. Note: this function uses a
205 | * `revert` opcode (which leaves remaining gas untouched) while Solidity
206 | * uses an invalid opcode to revert (consuming all remaining gas).
207 | *
208 | * Requirements:
209 | *
210 | * - The divisor cannot be zero.
211 | */
212 | function div(uint256 a, uint256 b) internal pure returns (uint256) {
213 | return div(a, b, "SafeMath: division by zero");
214 | }
215 |
216 | /**
217 | * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
218 | * division by zero. The result is rounded towards zero.
219 | *
220 | * Counterpart to Solidity's `/` operator. Note: this function uses a
221 | * `revert` opcode (which leaves remaining gas untouched) while Solidity
222 | * uses an invalid opcode to revert (consuming all remaining gas).
223 | *
224 | * Requirements:
225 | *
226 | * - The divisor cannot be zero.
227 | */
228 | function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
229 | require(b > 0, errorMessage);
230 | uint256 c = a / b;
231 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold
232 |
233 | return c;
234 | }
235 |
236 | /**
237 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
238 | * Reverts when dividing by zero.
239 | *
240 | * Counterpart to Solidity's `%` operator. This function uses a `revert`
241 | * opcode (which leaves remaining gas untouched) while Solidity uses an
242 | * invalid opcode to revert (consuming all remaining gas).
243 | *
244 | * Requirements:
245 | *
246 | * - The divisor cannot be zero.
247 | */
248 | function mod(uint256 a, uint256 b) internal pure returns (uint256) {
249 | return mod(a, b, "SafeMath: modulo by zero");
250 | }
251 |
252 | /**
253 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
254 | * Reverts with custom message when dividing by zero.
255 | *
256 | * Counterpart to Solidity's `%` operator. This function uses a `revert`
257 | * opcode (which leaves remaining gas untouched) while Solidity uses an
258 | * invalid opcode to revert (consuming all remaining gas).
259 | *
260 | * Requirements:
261 | *
262 | * - The divisor cannot be zero.
263 | */
264 | function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
265 | require(b != 0, errorMessage);
266 | return a % b;
267 | }
268 | }
269 |
270 |
271 | // Dependency file: @openzeppelin/contracts/token/ERC20/ERC20.sol
272 |
273 |
274 | // pragma solidity >=0.6.0 <0.8.0;
275 |
276 | // import "@openzeppelin/contracts/GSN/Context.sol";
277 | // import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
278 | // import "@openzeppelin/contracts/math/SafeMath.sol";
279 |
280 | /**
281 | * @dev Implementation of the {IERC20} interface.
282 | *
283 | * This implementation is agnostic to the way tokens are created. This means
284 | * that a supply mechanism has to be added in a derived contract using {_mint}.
285 | * For a generic mechanism see {ERC20PresetMinterPauser}.
286 | *
287 | * TIP: For a detailed writeup see our guide
288 | * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
289 | * to implement supply mechanisms].
290 | *
291 | * We have followed general OpenZeppelin guidelines: functions revert instead
292 | * of returning `false` on failure. This behavior is nonetheless conventional
293 | * and does not conflict with the expectations of ERC20 applications.
294 | *
295 | * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
296 | * This allows applications to reconstruct the allowance for all accounts just
297 | * by listening to said events. Other implementations of the EIP may not emit
298 | * these events, as it isn't required by the specification.
299 | *
300 | * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
301 | * functions have been added to mitigate the well-known issues around setting
302 | * allowances. See {IERC20-approve}.
303 | */
304 | contract ERC20 is Context, IERC20 {
305 | using SafeMath for uint256;
306 |
307 | mapping (address => uint256) private _balances;
308 |
309 | mapping (address => mapping (address => uint256)) private _allowances;
310 |
311 | uint256 private _totalSupply;
312 |
313 | string private _name;
314 | string private _symbol;
315 | uint8 private _decimals;
316 |
317 | /**
318 | * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
319 | * a default value of 18.
320 | *
321 | * To select a different value for {decimals}, use {_setupDecimals}.
322 | *
323 | * All three of these values are immutable: they can only be set once during
324 | * construction.
325 | */
326 | constructor (string memory name_, string memory symbol_) public {
327 | _name = name_;
328 | _symbol = symbol_;
329 | _decimals = 18;
330 | }
331 |
332 | /**
333 | * @dev Returns the name of the token.
334 | */
335 | function name() public view returns (string memory) {
336 | return _name;
337 | }
338 |
339 | /**
340 | * @dev Returns the symbol of the token, usually a shorter version of the
341 | * name.
342 | */
343 | function symbol() public view returns (string memory) {
344 | return _symbol;
345 | }
346 |
347 | /**
348 | * @dev Returns the number of decimals used to get its user representation.
349 | * For example, if `decimals` equals `2`, a balance of `505` tokens should
350 | * be displayed to a user as `5,05` (`505 / 10 ** 2`).
351 | *
352 | * Tokens usually opt for a value of 18, imitating the relationship between
353 | * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
354 | * called.
355 | *
356 | * NOTE: This information is only used for _display_ purposes: it in
357 | * no way affects any of the arithmetic of the contract, including
358 | * {IERC20-balanceOf} and {IERC20-transfer}.
359 | */
360 | function decimals() public view returns (uint8) {
361 | return _decimals;
362 | }
363 |
364 | /**
365 | * @dev See {IERC20-totalSupply}.
366 | */
367 | function totalSupply() public view override returns (uint256) {
368 | return _totalSupply;
369 | }
370 |
371 | /**
372 | * @dev See {IERC20-balanceOf}.
373 | */
374 | function balanceOf(address account) public view override returns (uint256) {
375 | return _balances[account];
376 | }
377 |
378 | /**
379 | * @dev See {IERC20-transfer}.
380 | *
381 | * Requirements:
382 | *
383 | * - `recipient` cannot be the zero address.
384 | * - the caller must have a balance of at least `amount`.
385 | */
386 | function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
387 | _transfer(_msgSender(), recipient, amount);
388 | return true;
389 | }
390 |
391 | /**
392 | * @dev See {IERC20-allowance}.
393 | */
394 | function allowance(address owner, address spender) public view virtual override returns (uint256) {
395 | return _allowances[owner][spender];
396 | }
397 |
398 | /**
399 | * @dev See {IERC20-approve}.
400 | *
401 | * Requirements:
402 | *
403 | * - `spender` cannot be the zero address.
404 | */
405 | function approve(address spender, uint256 amount) public virtual override returns (bool) {
406 | _approve(_msgSender(), spender, amount);
407 | return true;
408 | }
409 |
410 | /**
411 | * @dev See {IERC20-transferFrom}.
412 | *
413 | * Emits an {Approval} event indicating the updated allowance. This is not
414 | * required by the EIP. See the note at the beginning of {ERC20}.
415 | *
416 | * Requirements:
417 | *
418 | * - `sender` and `recipient` cannot be the zero address.
419 | * - `sender` must have a balance of at least `amount`.
420 | * - the caller must have allowance for ``sender``'s tokens of at least
421 | * `amount`.
422 | */
423 | function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
424 | _transfer(sender, recipient, amount);
425 | _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
426 | return true;
427 | }
428 |
429 | /**
430 | * @dev Atomically increases the allowance granted to `spender` by the caller.
431 | *
432 | * This is an alternative to {approve} that can be used as a mitigation for
433 | * problems described in {IERC20-approve}.
434 | *
435 | * Emits an {Approval} event indicating the updated allowance.
436 | *
437 | * Requirements:
438 | *
439 | * - `spender` cannot be the zero address.
440 | */
441 | function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
442 | _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
443 | return true;
444 | }
445 |
446 | /**
447 | * @dev Atomically decreases the allowance granted to `spender` by the caller.
448 | *
449 | * This is an alternative to {approve} that can be used as a mitigation for
450 | * problems described in {IERC20-approve}.
451 | *
452 | * Emits an {Approval} event indicating the updated allowance.
453 | *
454 | * Requirements:
455 | *
456 | * - `spender` cannot be the zero address.
457 | * - `spender` must have allowance for the caller of at least
458 | * `subtractedValue`.
459 | */
460 | function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
461 | _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
462 | return true;
463 | }
464 |
465 | /**
466 | * @dev Moves tokens `amount` from `sender` to `recipient`.
467 | *
468 | * This is internal function is equivalent to {transfer}, and can be used to
469 | * e.g. implement automatic token fees, slashing mechanisms, etc.
470 | *
471 | * Emits a {Transfer} event.
472 | *
473 | * Requirements:
474 | *
475 | * - `sender` cannot be the zero address.
476 | * - `recipient` cannot be the zero address.
477 | * - `sender` must have a balance of at least `amount`.
478 | */
479 | function _transfer(address sender, address recipient, uint256 amount) internal virtual {
480 | require(sender != address(0), "ERC20: transfer from the zero address");
481 | require(recipient != address(0), "ERC20: transfer to the zero address");
482 |
483 | _beforeTokenTransfer(sender, recipient, amount);
484 |
485 | _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
486 | _balances[recipient] = _balances[recipient].add(amount);
487 | emit Transfer(sender, recipient, amount);
488 | }
489 |
490 | /** @dev Creates `amount` tokens and assigns them to `account`, increasing
491 | * the total supply.
492 | *
493 | * Emits a {Transfer} event with `from` set to the zero address.
494 | *
495 | * Requirements:
496 | *
497 | * - `to` cannot be the zero address.
498 | */
499 | function _mint(address account, uint256 amount) internal virtual {
500 | require(account != address(0), "ERC20: mint to the zero address");
501 |
502 | _beforeTokenTransfer(address(0), account, amount);
503 |
504 | _totalSupply = _totalSupply.add(amount);
505 | _balances[account] = _balances[account].add(amount);
506 | emit Transfer(address(0), account, amount);
507 | }
508 |
509 | /**
510 | * @dev Destroys `amount` tokens from `account`, reducing the
511 | * total supply.
512 | *
513 | * Emits a {Transfer} event with `to` set to the zero address.
514 | *
515 | * Requirements:
516 | *
517 | * - `account` cannot be the zero address.
518 | * - `account` must have at least `amount` tokens.
519 | */
520 | function _burn(address account, uint256 amount) internal virtual {
521 | require(account != address(0), "ERC20: burn from the zero address");
522 |
523 | _beforeTokenTransfer(account, address(0), amount);
524 |
525 | _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
526 | _totalSupply = _totalSupply.sub(amount);
527 | emit Transfer(account, address(0), amount);
528 | }
529 |
530 | /**
531 | * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
532 | *
533 | * This internal function is equivalent to `approve`, and can be used to
534 | * e.g. set automatic allowances for certain subsystems, etc.
535 | *
536 | * Emits an {Approval} event.
537 | *
538 | * Requirements:
539 | *
540 | * - `owner` cannot be the zero address.
541 | * - `spender` cannot be the zero address.
542 | */
543 | function _approve(address owner, address spender, uint256 amount) internal virtual {
544 | require(owner != address(0), "ERC20: approve from the zero address");
545 | require(spender != address(0), "ERC20: approve to the zero address");
546 |
547 | _allowances[owner][spender] = amount;
548 | emit Approval(owner, spender, amount);
549 | }
550 |
551 | /**
552 | * @dev Sets {decimals} to a value other than the default one of 18.
553 | *
554 | * WARNING: This function should only be called from the constructor. Most
555 | * applications that interact with token contracts will not expect
556 | * {decimals} to ever change, and may work incorrectly if it does.
557 | */
558 | function _setupDecimals(uint8 decimals_) internal {
559 | _decimals = decimals_;
560 | }
561 |
562 | /**
563 | * @dev Hook that is called before any transfer of tokens. This includes
564 | * minting and burning.
565 | *
566 | * Calling conditions:
567 | *
568 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
569 | * will be to transferred to `to`.
570 | * - when `from` is zero, `amount` tokens will be minted for `to`.
571 | * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
572 | * - `from` and `to` are never both zero.
573 | *
574 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
575 | */
576 | function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
577 | }
578 |
579 |
580 | // Root file: contracts/test/token_safe_transfer.sol
581 |
582 |
583 | // used for running automated hardhat tests
584 |
585 | pragma solidity >=0.7.5 <0.8.0;
586 |
587 | // import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
588 |
589 | contract TokenSafeTransfer is ERC20 {
590 |
591 | bool private initialTransfer;
592 |
593 | constructor() public ERC20("TokenSafe", "TKNSF") {
594 | _mint(msg.sender, 1000000000000000000000000);
595 | }
596 |
597 | function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
598 | if(initialTransfer == false) {
599 | _transfer(_msgSender(), recipient, amount);
600 | initialTransfer = true;
601 | } else {
602 | require(false, 'Token transfer failed!'); // this is for testing safeTransfer
603 | }
604 | return true;
605 | }
606 | }
607 |
--------------------------------------------------------------------------------
/flatten/uniswap_v2_pair.sol:
--------------------------------------------------------------------------------
1 | // Dependency file: @openzeppelin/contracts/GSN/Context.sol
2 |
3 | // SPDX-License-Identifier: MIT
4 |
5 | // pragma solidity >=0.6.0 <0.8.0;
6 |
7 | /*
8 | * @dev Provides information about the current execution context, including the
9 | * sender of the transaction and its data. While these are generally available
10 | * via msg.sender and msg.data, they should not be accessed in such a direct
11 | * manner, since when dealing with GSN meta-transactions the account sending and
12 | * paying for execution may not be the actual sender (as far as an application
13 | * is concerned).
14 | *
15 | * This contract is only required for intermediate, library-like contracts.
16 | */
17 | abstract contract Context {
18 | function _msgSender() internal view virtual returns (address payable) {
19 | return msg.sender;
20 | }
21 |
22 | function _msgData() internal view virtual returns (bytes memory) {
23 | this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
24 | return msg.data;
25 | }
26 | }
27 |
28 |
29 | // Dependency file: @openzeppelin/contracts/token/ERC20/IERC20.sol
30 |
31 |
32 | // pragma solidity >=0.6.0 <0.8.0;
33 |
34 | /**
35 | * @dev Interface of the ERC20 standard as defined in the EIP.
36 | */
37 | interface IERC20 {
38 | /**
39 | * @dev Returns the amount of tokens in existence.
40 | */
41 | function totalSupply() external view returns (uint256);
42 |
43 | /**
44 | * @dev Returns the amount of tokens owned by `account`.
45 | */
46 | function balanceOf(address account) external view returns (uint256);
47 |
48 | /**
49 | * @dev Moves `amount` tokens from the caller's account to `recipient`.
50 | *
51 | * Returns a boolean value indicating whether the operation succeeded.
52 | *
53 | * Emits a {Transfer} event.
54 | */
55 | function transfer(address recipient, uint256 amount) external returns (bool);
56 |
57 | /**
58 | * @dev Returns the remaining number of tokens that `spender` will be
59 | * allowed to spend on behalf of `owner` through {transferFrom}. This is
60 | * zero by default.
61 | *
62 | * This value changes when {approve} or {transferFrom} are called.
63 | */
64 | function allowance(address owner, address spender) external view returns (uint256);
65 |
66 | /**
67 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
68 | *
69 | * Returns a boolean value indicating whether the operation succeeded.
70 | *
71 | * // importANT: Beware that changing an allowance with this method brings the risk
72 | * that someone may use both the old and the new allowance by unfortunate
73 | * transaction ordering. One possible solution to mitigate this race
74 | * condition is to first reduce the spender's allowance to 0 and set the
75 | * desired value afterwards:
76 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
77 | *
78 | * Emits an {Approval} event.
79 | */
80 | function approve(address spender, uint256 amount) external returns (bool);
81 |
82 | /**
83 | * @dev Moves `amount` tokens from `sender` to `recipient` using the
84 | * allowance mechanism. `amount` is then deducted from the caller's
85 | * allowance.
86 | *
87 | * Returns a boolean value indicating whether the operation succeeded.
88 | *
89 | * Emits a {Transfer} event.
90 | */
91 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
92 |
93 | /**
94 | * @dev Emitted when `value` tokens are moved from one account (`from`) to
95 | * another (`to`).
96 | *
97 | * Note that `value` may be zero.
98 | */
99 | event Transfer(address indexed from, address indexed to, uint256 value);
100 |
101 | /**
102 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by
103 | * a call to {approve}. `value` is the new allowance.
104 | */
105 | event Approval(address indexed owner, address indexed spender, uint256 value);
106 | }
107 |
108 |
109 | // Dependency file: @openzeppelin/contracts/math/SafeMath.sol
110 |
111 |
112 | // pragma solidity >=0.6.0 <0.8.0;
113 |
114 | /**
115 | * @dev Wrappers over Solidity's arithmetic operations with added overflow
116 | * checks.
117 | *
118 | * Arithmetic operations in Solidity wrap on overflow. This can easily result
119 | * in bugs, because programmers usually assume that an overflow raises an
120 | * error, which is the standard behavior in high level programming languages.
121 | * `SafeMath` restores this intuition by reverting the transaction when an
122 | * operation overflows.
123 | *
124 | * Using this library instead of the unchecked operations eliminates an entire
125 | * class of bugs, so it's recommended to use it always.
126 | */
127 | library SafeMath {
128 | /**
129 | * @dev Returns the addition of two unsigned integers, reverting on
130 | * overflow.
131 | *
132 | * Counterpart to Solidity's `+` operator.
133 | *
134 | * Requirements:
135 | *
136 | * - Addition cannot overflow.
137 | */
138 | function add(uint256 a, uint256 b) internal pure returns (uint256) {
139 | uint256 c = a + b;
140 | require(c >= a, "SafeMath: addition overflow");
141 |
142 | return c;
143 | }
144 |
145 | /**
146 | * @dev Returns the subtraction of two unsigned integers, reverting on
147 | * overflow (when the result is negative).
148 | *
149 | * Counterpart to Solidity's `-` operator.
150 | *
151 | * Requirements:
152 | *
153 | * - Subtraction cannot overflow.
154 | */
155 | function sub(uint256 a, uint256 b) internal pure returns (uint256) {
156 | return sub(a, b, "SafeMath: subtraction overflow");
157 | }
158 |
159 | /**
160 | * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
161 | * overflow (when the result is negative).
162 | *
163 | * Counterpart to Solidity's `-` operator.
164 | *
165 | * Requirements:
166 | *
167 | * - Subtraction cannot overflow.
168 | */
169 | function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
170 | require(b <= a, errorMessage);
171 | uint256 c = a - b;
172 |
173 | return c;
174 | }
175 |
176 | /**
177 | * @dev Returns the multiplication of two unsigned integers, reverting on
178 | * overflow.
179 | *
180 | * Counterpart to Solidity's `*` operator.
181 | *
182 | * Requirements:
183 | *
184 | * - Multiplication cannot overflow.
185 | */
186 | function mul(uint256 a, uint256 b) internal pure returns (uint256) {
187 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
188 | // benefit is lost if 'b' is also tested.
189 | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
190 | if (a == 0) {
191 | return 0;
192 | }
193 |
194 | uint256 c = a * b;
195 | require(c / a == b, "SafeMath: multiplication overflow");
196 |
197 | return c;
198 | }
199 |
200 | /**
201 | * @dev Returns the integer division of two unsigned integers. Reverts on
202 | * division by zero. The result is rounded towards zero.
203 | *
204 | * Counterpart to Solidity's `/` operator. Note: this function uses a
205 | * `revert` opcode (which leaves remaining gas untouched) while Solidity
206 | * uses an invalid opcode to revert (consuming all remaining gas).
207 | *
208 | * Requirements:
209 | *
210 | * - The divisor cannot be zero.
211 | */
212 | function div(uint256 a, uint256 b) internal pure returns (uint256) {
213 | return div(a, b, "SafeMath: division by zero");
214 | }
215 |
216 | /**
217 | * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
218 | * division by zero. The result is rounded towards zero.
219 | *
220 | * Counterpart to Solidity's `/` operator. Note: this function uses a
221 | * `revert` opcode (which leaves remaining gas untouched) while Solidity
222 | * uses an invalid opcode to revert (consuming all remaining gas).
223 | *
224 | * Requirements:
225 | *
226 | * - The divisor cannot be zero.
227 | */
228 | function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
229 | require(b > 0, errorMessage);
230 | uint256 c = a / b;
231 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold
232 |
233 | return c;
234 | }
235 |
236 | /**
237 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
238 | * Reverts when dividing by zero.
239 | *
240 | * Counterpart to Solidity's `%` operator. This function uses a `revert`
241 | * opcode (which leaves remaining gas untouched) while Solidity uses an
242 | * invalid opcode to revert (consuming all remaining gas).
243 | *
244 | * Requirements:
245 | *
246 | * - The divisor cannot be zero.
247 | */
248 | function mod(uint256 a, uint256 b) internal pure returns (uint256) {
249 | return mod(a, b, "SafeMath: modulo by zero");
250 | }
251 |
252 | /**
253 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
254 | * Reverts with custom message when dividing by zero.
255 | *
256 | * Counterpart to Solidity's `%` operator. This function uses a `revert`
257 | * opcode (which leaves remaining gas untouched) while Solidity uses an
258 | * invalid opcode to revert (consuming all remaining gas).
259 | *
260 | * Requirements:
261 | *
262 | * - The divisor cannot be zero.
263 | */
264 | function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
265 | require(b != 0, errorMessage);
266 | return a % b;
267 | }
268 | }
269 |
270 |
271 | // Dependency file: @openzeppelin/contracts/token/ERC20/ERC20.sol
272 |
273 |
274 | // pragma solidity >=0.6.0 <0.8.0;
275 |
276 | // import "@openzeppelin/contracts/GSN/Context.sol";
277 | // import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
278 | // import "@openzeppelin/contracts/math/SafeMath.sol";
279 |
280 | /**
281 | * @dev Implementation of the {IERC20} interface.
282 | *
283 | * This implementation is agnostic to the way tokens are created. This means
284 | * that a supply mechanism has to be added in a derived contract using {_mint}.
285 | * For a generic mechanism see {ERC20PresetMinterPauser}.
286 | *
287 | * TIP: For a detailed writeup see our guide
288 | * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
289 | * to implement supply mechanisms].
290 | *
291 | * We have followed general OpenZeppelin guidelines: functions revert instead
292 | * of returning `false` on failure. This behavior is nonetheless conventional
293 | * and does not conflict with the expectations of ERC20 applications.
294 | *
295 | * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
296 | * This allows applications to reconstruct the allowance for all accounts just
297 | * by listening to said events. Other implementations of the EIP may not emit
298 | * these events, as it isn't required by the specification.
299 | *
300 | * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
301 | * functions have been added to mitigate the well-known issues around setting
302 | * allowances. See {IERC20-approve}.
303 | */
304 | contract ERC20 is Context, IERC20 {
305 | using SafeMath for uint256;
306 |
307 | mapping (address => uint256) private _balances;
308 |
309 | mapping (address => mapping (address => uint256)) private _allowances;
310 |
311 | uint256 private _totalSupply;
312 |
313 | string private _name;
314 | string private _symbol;
315 | uint8 private _decimals;
316 |
317 | /**
318 | * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
319 | * a default value of 18.
320 | *
321 | * To select a different value for {decimals}, use {_setupDecimals}.
322 | *
323 | * All three of these values are immutable: they can only be set once during
324 | * construction.
325 | */
326 | constructor (string memory name_, string memory symbol_) public {
327 | _name = name_;
328 | _symbol = symbol_;
329 | _decimals = 18;
330 | }
331 |
332 | /**
333 | * @dev Returns the name of the token.
334 | */
335 | function name() public view returns (string memory) {
336 | return _name;
337 | }
338 |
339 | /**
340 | * @dev Returns the symbol of the token, usually a shorter version of the
341 | * name.
342 | */
343 | function symbol() public view returns (string memory) {
344 | return _symbol;
345 | }
346 |
347 | /**
348 | * @dev Returns the number of decimals used to get its user representation.
349 | * For example, if `decimals` equals `2`, a balance of `505` tokens should
350 | * be displayed to a user as `5,05` (`505 / 10 ** 2`).
351 | *
352 | * Tokens usually opt for a value of 18, imitating the relationship between
353 | * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
354 | * called.
355 | *
356 | * NOTE: This information is only used for _display_ purposes: it in
357 | * no way affects any of the arithmetic of the contract, including
358 | * {IERC20-balanceOf} and {IERC20-transfer}.
359 | */
360 | function decimals() public view returns (uint8) {
361 | return _decimals;
362 | }
363 |
364 | /**
365 | * @dev See {IERC20-totalSupply}.
366 | */
367 | function totalSupply() public view override returns (uint256) {
368 | return _totalSupply;
369 | }
370 |
371 | /**
372 | * @dev See {IERC20-balanceOf}.
373 | */
374 | function balanceOf(address account) public view override returns (uint256) {
375 | return _balances[account];
376 | }
377 |
378 | /**
379 | * @dev See {IERC20-transfer}.
380 | *
381 | * Requirements:
382 | *
383 | * - `recipient` cannot be the zero address.
384 | * - the caller must have a balance of at least `amount`.
385 | */
386 | function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
387 | _transfer(_msgSender(), recipient, amount);
388 | return true;
389 | }
390 |
391 | /**
392 | * @dev See {IERC20-allowance}.
393 | */
394 | function allowance(address owner, address spender) public view virtual override returns (uint256) {
395 | return _allowances[owner][spender];
396 | }
397 |
398 | /**
399 | * @dev See {IERC20-approve}.
400 | *
401 | * Requirements:
402 | *
403 | * - `spender` cannot be the zero address.
404 | */
405 | function approve(address spender, uint256 amount) public virtual override returns (bool) {
406 | _approve(_msgSender(), spender, amount);
407 | return true;
408 | }
409 |
410 | /**
411 | * @dev See {IERC20-transferFrom}.
412 | *
413 | * Emits an {Approval} event indicating the updated allowance. This is not
414 | * required by the EIP. See the note at the beginning of {ERC20}.
415 | *
416 | * Requirements:
417 | *
418 | * - `sender` and `recipient` cannot be the zero address.
419 | * - `sender` must have a balance of at least `amount`.
420 | * - the caller must have allowance for ``sender``'s tokens of at least
421 | * `amount`.
422 | */
423 | function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
424 | _transfer(sender, recipient, amount);
425 | _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
426 | return true;
427 | }
428 |
429 | /**
430 | * @dev Atomically increases the allowance granted to `spender` by the caller.
431 | *
432 | * This is an alternative to {approve} that can be used as a mitigation for
433 | * problems described in {IERC20-approve}.
434 | *
435 | * Emits an {Approval} event indicating the updated allowance.
436 | *
437 | * Requirements:
438 | *
439 | * - `spender` cannot be the zero address.
440 | */
441 | function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
442 | _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
443 | return true;
444 | }
445 |
446 | /**
447 | * @dev Atomically decreases the allowance granted to `spender` by the caller.
448 | *
449 | * This is an alternative to {approve} that can be used as a mitigation for
450 | * problems described in {IERC20-approve}.
451 | *
452 | * Emits an {Approval} event indicating the updated allowance.
453 | *
454 | * Requirements:
455 | *
456 | * - `spender` cannot be the zero address.
457 | * - `spender` must have allowance for the caller of at least
458 | * `subtractedValue`.
459 | */
460 | function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
461 | _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
462 | return true;
463 | }
464 |
465 | /**
466 | * @dev Moves tokens `amount` from `sender` to `recipient`.
467 | *
468 | * This is internal function is equivalent to {transfer}, and can be used to
469 | * e.g. implement automatic token fees, slashing mechanisms, etc.
470 | *
471 | * Emits a {Transfer} event.
472 | *
473 | * Requirements:
474 | *
475 | * - `sender` cannot be the zero address.
476 | * - `recipient` cannot be the zero address.
477 | * - `sender` must have a balance of at least `amount`.
478 | */
479 | function _transfer(address sender, address recipient, uint256 amount) internal virtual {
480 | require(sender != address(0), "ERC20: transfer from the zero address");
481 | require(recipient != address(0), "ERC20: transfer to the zero address");
482 |
483 | _beforeTokenTransfer(sender, recipient, amount);
484 |
485 | _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
486 | _balances[recipient] = _balances[recipient].add(amount);
487 | emit Transfer(sender, recipient, amount);
488 | }
489 |
490 | /** @dev Creates `amount` tokens and assigns them to `account`, increasing
491 | * the total supply.
492 | *
493 | * Emits a {Transfer} event with `from` set to the zero address.
494 | *
495 | * Requirements:
496 | *
497 | * - `to` cannot be the zero address.
498 | */
499 | function _mint(address account, uint256 amount) internal virtual {
500 | require(account != address(0), "ERC20: mint to the zero address");
501 |
502 | _beforeTokenTransfer(address(0), account, amount);
503 |
504 | _totalSupply = _totalSupply.add(amount);
505 | _balances[account] = _balances[account].add(amount);
506 | emit Transfer(address(0), account, amount);
507 | }
508 |
509 | /**
510 | * @dev Destroys `amount` tokens from `account`, reducing the
511 | * total supply.
512 | *
513 | * Emits a {Transfer} event with `to` set to the zero address.
514 | *
515 | * Requirements:
516 | *
517 | * - `account` cannot be the zero address.
518 | * - `account` must have at least `amount` tokens.
519 | */
520 | function _burn(address account, uint256 amount) internal virtual {
521 | require(account != address(0), "ERC20: burn from the zero address");
522 |
523 | _beforeTokenTransfer(account, address(0), amount);
524 |
525 | _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
526 | _totalSupply = _totalSupply.sub(amount);
527 | emit Transfer(account, address(0), amount);
528 | }
529 |
530 | /**
531 | * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
532 | *
533 | * This internal function is equivalent to `approve`, and can be used to
534 | * e.g. set automatic allowances for certain subsystems, etc.
535 | *
536 | * Emits an {Approval} event.
537 | *
538 | * Requirements:
539 | *
540 | * - `owner` cannot be the zero address.
541 | * - `spender` cannot be the zero address.
542 | */
543 | function _approve(address owner, address spender, uint256 amount) internal virtual {
544 | require(owner != address(0), "ERC20: approve from the zero address");
545 | require(spender != address(0), "ERC20: approve to the zero address");
546 |
547 | _allowances[owner][spender] = amount;
548 | emit Approval(owner, spender, amount);
549 | }
550 |
551 | /**
552 | * @dev Sets {decimals} to a value other than the default one of 18.
553 | *
554 | * WARNING: This function should only be called from the constructor. Most
555 | * applications that interact with token contracts will not expect
556 | * {decimals} to ever change, and may work incorrectly if it does.
557 | */
558 | function _setupDecimals(uint8 decimals_) internal {
559 | _decimals = decimals_;
560 | }
561 |
562 | /**
563 | * @dev Hook that is called before any transfer of tokens. This includes
564 | * minting and burning.
565 | *
566 | * Calling conditions:
567 | *
568 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
569 | * will be to transferred to `to`.
570 | * - when `from` is zero, `amount` tokens will be minted for `to`.
571 | * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
572 | * - `from` and `to` are never both zero.
573 | *
574 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
575 | */
576 | function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
577 | }
578 |
579 |
580 | // Root file: contracts/test/uniswap_v2_pair.sol
581 |
582 |
583 | // used for running automated hardhat tests
584 |
585 | pragma solidity >=0.7.5 <0.8.0;
586 |
587 | // import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
588 |
589 | contract UniswapV2Pair is ERC20 {
590 |
591 | uint112 private reserve0;
592 | uint112 private reserve1;
593 | address public token0;
594 |
595 | constructor(uint256 totalSupply, address _token0, uint112 _reserve0, uint112 _reserve1) public ERC20("Uniswap V2", "UNI-V2") {
596 | _mint(msg.sender, totalSupply);
597 | reserve0 = _reserve0;
598 | reserve1 = _reserve1;
599 | token0 = _token0;
600 | }
601 |
602 | function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
603 | _reserve0 = reserve0;
604 | _reserve1 = reserve1;
605 | _blockTimestampLast = uint32(block.timestamp);
606 | }
607 | }
608 |
--------------------------------------------------------------------------------
/flatten/DePayLiquidityStaking.sol:
--------------------------------------------------------------------------------
1 | // Dependency file: contracts/interfaces/IUniswapV2Pair.sol
2 |
3 | // SPDX-License-Identifier: MIT
4 |
5 | // pragma solidity >=0.7.5 <0.8.0;
6 |
7 | interface IUniswapV2Pair {
8 |
9 | function totalSupply() external view returns (uint);
10 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
11 | function transfer(address to, uint value) external returns (bool);
12 | function transferFrom(address from, address to, uint value) external returns (bool);
13 | function token0() external view returns (address);
14 |
15 | }
16 |
17 |
18 | // Dependency file: @openzeppelin/contracts/token/ERC20/IERC20.sol
19 |
20 |
21 | // pragma solidity >=0.6.0 <0.8.0;
22 |
23 | /**
24 | * @dev Interface of the ERC20 standard as defined in the EIP.
25 | */
26 | interface IERC20 {
27 | /**
28 | * @dev Returns the amount of tokens in existence.
29 | */
30 | function totalSupply() external view returns (uint256);
31 |
32 | /**
33 | * @dev Returns the amount of tokens owned by `account`.
34 | */
35 | function balanceOf(address account) external view returns (uint256);
36 |
37 | /**
38 | * @dev Moves `amount` tokens from the caller's account to `recipient`.
39 | *
40 | * Returns a boolean value indicating whether the operation succeeded.
41 | *
42 | * Emits a {Transfer} event.
43 | */
44 | function transfer(address recipient, uint256 amount) external returns (bool);
45 |
46 | /**
47 | * @dev Returns the remaining number of tokens that `spender` will be
48 | * allowed to spend on behalf of `owner` through {transferFrom}. This is
49 | * zero by default.
50 | *
51 | * This value changes when {approve} or {transferFrom} are called.
52 | */
53 | function allowance(address owner, address spender) external view returns (uint256);
54 |
55 | /**
56 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
57 | *
58 | * Returns a boolean value indicating whether the operation succeeded.
59 | *
60 | * // importANT: Beware that changing an allowance with this method brings the risk
61 | * that someone may use both the old and the new allowance by unfortunate
62 | * transaction ordering. One possible solution to mitigate this race
63 | * condition is to first reduce the spender's allowance to 0 and set the
64 | * desired value afterwards:
65 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
66 | *
67 | * Emits an {Approval} event.
68 | */
69 | function approve(address spender, uint256 amount) external returns (bool);
70 |
71 | /**
72 | * @dev Moves `amount` tokens from `sender` to `recipient` using the
73 | * allowance mechanism. `amount` is then deducted from the caller's
74 | * allowance.
75 | *
76 | * Returns a boolean value indicating whether the operation succeeded.
77 | *
78 | * Emits a {Transfer} event.
79 | */
80 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
81 |
82 | /**
83 | * @dev Emitted when `value` tokens are moved from one account (`from`) to
84 | * another (`to`).
85 | *
86 | * Note that `value` may be zero.
87 | */
88 | event Transfer(address indexed from, address indexed to, uint256 value);
89 |
90 | /**
91 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by
92 | * a call to {approve}. `value` is the new allowance.
93 | */
94 | event Approval(address indexed owner, address indexed spender, uint256 value);
95 | }
96 |
97 |
98 | // Dependency file: @openzeppelin/contracts/math/SafeMath.sol
99 |
100 |
101 | // pragma solidity >=0.6.0 <0.8.0;
102 |
103 | /**
104 | * @dev Wrappers over Solidity's arithmetic operations with added overflow
105 | * checks.
106 | *
107 | * Arithmetic operations in Solidity wrap on overflow. This can easily result
108 | * in bugs, because programmers usually assume that an overflow raises an
109 | * error, which is the standard behavior in high level programming languages.
110 | * `SafeMath` restores this intuition by reverting the transaction when an
111 | * operation overflows.
112 | *
113 | * Using this library instead of the unchecked operations eliminates an entire
114 | * class of bugs, so it's recommended to use it always.
115 | */
116 | library SafeMath {
117 | /**
118 | * @dev Returns the addition of two unsigned integers, reverting on
119 | * overflow.
120 | *
121 | * Counterpart to Solidity's `+` operator.
122 | *
123 | * Requirements:
124 | *
125 | * - Addition cannot overflow.
126 | */
127 | function add(uint256 a, uint256 b) internal pure returns (uint256) {
128 | uint256 c = a + b;
129 | require(c >= a, "SafeMath: addition overflow");
130 |
131 | return c;
132 | }
133 |
134 | /**
135 | * @dev Returns the subtraction of two unsigned integers, reverting on
136 | * overflow (when the result is negative).
137 | *
138 | * Counterpart to Solidity's `-` operator.
139 | *
140 | * Requirements:
141 | *
142 | * - Subtraction cannot overflow.
143 | */
144 | function sub(uint256 a, uint256 b) internal pure returns (uint256) {
145 | return sub(a, b, "SafeMath: subtraction overflow");
146 | }
147 |
148 | /**
149 | * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
150 | * overflow (when the result is negative).
151 | *
152 | * Counterpart to Solidity's `-` operator.
153 | *
154 | * Requirements:
155 | *
156 | * - Subtraction cannot overflow.
157 | */
158 | function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
159 | require(b <= a, errorMessage);
160 | uint256 c = a - b;
161 |
162 | return c;
163 | }
164 |
165 | /**
166 | * @dev Returns the multiplication of two unsigned integers, reverting on
167 | * overflow.
168 | *
169 | * Counterpart to Solidity's `*` operator.
170 | *
171 | * Requirements:
172 | *
173 | * - Multiplication cannot overflow.
174 | */
175 | function mul(uint256 a, uint256 b) internal pure returns (uint256) {
176 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
177 | // benefit is lost if 'b' is also tested.
178 | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
179 | if (a == 0) {
180 | return 0;
181 | }
182 |
183 | uint256 c = a * b;
184 | require(c / a == b, "SafeMath: multiplication overflow");
185 |
186 | return c;
187 | }
188 |
189 | /**
190 | * @dev Returns the integer division of two unsigned integers. Reverts on
191 | * division by zero. The result is rounded towards zero.
192 | *
193 | * Counterpart to Solidity's `/` operator. Note: this function uses a
194 | * `revert` opcode (which leaves remaining gas untouched) while Solidity
195 | * uses an invalid opcode to revert (consuming all remaining gas).
196 | *
197 | * Requirements:
198 | *
199 | * - The divisor cannot be zero.
200 | */
201 | function div(uint256 a, uint256 b) internal pure returns (uint256) {
202 | return div(a, b, "SafeMath: division by zero");
203 | }
204 |
205 | /**
206 | * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
207 | * division by zero. The result is rounded towards zero.
208 | *
209 | * Counterpart to Solidity's `/` operator. Note: this function uses a
210 | * `revert` opcode (which leaves remaining gas untouched) while Solidity
211 | * uses an invalid opcode to revert (consuming all remaining gas).
212 | *
213 | * Requirements:
214 | *
215 | * - The divisor cannot be zero.
216 | */
217 | function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
218 | require(b > 0, errorMessage);
219 | uint256 c = a / b;
220 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold
221 |
222 | return c;
223 | }
224 |
225 | /**
226 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
227 | * Reverts when dividing by zero.
228 | *
229 | * Counterpart to Solidity's `%` operator. This function uses a `revert`
230 | * opcode (which leaves remaining gas untouched) while Solidity uses an
231 | * invalid opcode to revert (consuming all remaining gas).
232 | *
233 | * Requirements:
234 | *
235 | * - The divisor cannot be zero.
236 | */
237 | function mod(uint256 a, uint256 b) internal pure returns (uint256) {
238 | return mod(a, b, "SafeMath: modulo by zero");
239 | }
240 |
241 | /**
242 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
243 | * Reverts with custom message when dividing by zero.
244 | *
245 | * Counterpart to Solidity's `%` operator. This function uses a `revert`
246 | * opcode (which leaves remaining gas untouched) while Solidity uses an
247 | * invalid opcode to revert (consuming all remaining gas).
248 | *
249 | * Requirements:
250 | *
251 | * - The divisor cannot be zero.
252 | */
253 | function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
254 | require(b != 0, errorMessage);
255 | return a % b;
256 | }
257 | }
258 |
259 |
260 | // Dependency file: @openzeppelin/contracts/utils/Address.sol
261 |
262 |
263 | // pragma solidity >=0.6.2 <0.8.0;
264 |
265 | /**
266 | * @dev Collection of functions related to the address type
267 | */
268 | library Address {
269 | /**
270 | * @dev Returns true if `account` is a contract.
271 | *
272 | * [// importANT]
273 | * ====
274 | * It is unsafe to assume that an address for which this function returns
275 | * false is an externally-owned account (EOA) and not a contract.
276 | *
277 | * Among others, `isContract` will return false for the following
278 | * types of addresses:
279 | *
280 | * - an externally-owned account
281 | * - a contract in construction
282 | * - an address where a contract will be created
283 | * - an address where a contract lived, but was destroyed
284 | * ====
285 | */
286 | function isContract(address account) internal view returns (bool) {
287 | // This method relies on extcodesize, which returns 0 for contracts in
288 | // construction, since the code is only stored at the end of the
289 | // constructor execution.
290 |
291 | uint256 size;
292 | // solhint-disable-next-line no-inline-assembly
293 | assembly { size := extcodesize(account) }
294 | return size > 0;
295 | }
296 |
297 | /**
298 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
299 | * `recipient`, forwarding all available gas and reverting on errors.
300 | *
301 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
302 | * of certain opcodes, possibly making contracts go over the 2300 gas limit
303 | * imposed by `transfer`, making them unable to receive funds via
304 | * `transfer`. {sendValue} removes this limitation.
305 | *
306 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
307 | *
308 | * // importANT: because control is transferred to `recipient`, care must be
309 | * taken to not create reentrancy vulnerabilities. Consider using
310 | * {ReentrancyGuard} or the
311 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
312 | */
313 | function sendValue(address payable recipient, uint256 amount) internal {
314 | require(address(this).balance >= amount, "Address: insufficient balance");
315 |
316 | // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
317 | (bool success, ) = recipient.call{ value: amount }("");
318 | require(success, "Address: unable to send value, recipient may have reverted");
319 | }
320 |
321 | /**
322 | * @dev Performs a Solidity function call using a low level `call`. A
323 | * plain`call` is an unsafe replacement for a function call: use this
324 | * function instead.
325 | *
326 | * If `target` reverts with a revert reason, it is bubbled up by this
327 | * function (like regular Solidity function calls).
328 | *
329 | * Returns the raw returned data. To convert to the expected return value,
330 | * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
331 | *
332 | * Requirements:
333 | *
334 | * - `target` must be a contract.
335 | * - calling `target` with `data` must not revert.
336 | *
337 | * _Available since v3.1._
338 | */
339 | function functionCall(address target, bytes memory data) internal returns (bytes memory) {
340 | return functionCall(target, data, "Address: low-level call failed");
341 | }
342 |
343 | /**
344 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
345 | * `errorMessage` as a fallback revert reason when `target` reverts.
346 | *
347 | * _Available since v3.1._
348 | */
349 | function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
350 | return functionCallWithValue(target, data, 0, errorMessage);
351 | }
352 |
353 | /**
354 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
355 | * but also transferring `value` wei to `target`.
356 | *
357 | * Requirements:
358 | *
359 | * - the calling contract must have an ETH balance of at least `value`.
360 | * - the called Solidity function must be `payable`.
361 | *
362 | * _Available since v3.1._
363 | */
364 | function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
365 | return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
366 | }
367 |
368 | /**
369 | * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
370 | * with `errorMessage` as a fallback revert reason when `target` reverts.
371 | *
372 | * _Available since v3.1._
373 | */
374 | function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
375 | require(address(this).balance >= value, "Address: insufficient balance for call");
376 | require(isContract(target), "Address: call to non-contract");
377 |
378 | // solhint-disable-next-line avoid-low-level-calls
379 | (bool success, bytes memory returndata) = target.call{ value: value }(data);
380 | return _verifyCallResult(success, returndata, errorMessage);
381 | }
382 |
383 | /**
384 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
385 | * but performing a static call.
386 | *
387 | * _Available since v3.3._
388 | */
389 | function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
390 | return functionStaticCall(target, data, "Address: low-level static call failed");
391 | }
392 |
393 | /**
394 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
395 | * but performing a static call.
396 | *
397 | * _Available since v3.3._
398 | */
399 | function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
400 | require(isContract(target), "Address: static call to non-contract");
401 |
402 | // solhint-disable-next-line avoid-low-level-calls
403 | (bool success, bytes memory returndata) = target.staticcall(data);
404 | return _verifyCallResult(success, returndata, errorMessage);
405 | }
406 |
407 | function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
408 | if (success) {
409 | return returndata;
410 | } else {
411 | // Look for revert reason and bubble it up if present
412 | if (returndata.length > 0) {
413 | // The easiest way to bubble the revert reason is using memory via assembly
414 |
415 | // solhint-disable-next-line no-inline-assembly
416 | assembly {
417 | let returndata_size := mload(returndata)
418 | revert(add(32, returndata), returndata_size)
419 | }
420 | } else {
421 | revert(errorMessage);
422 | }
423 | }
424 | }
425 | }
426 |
427 |
428 | // Dependency file: @openzeppelin/contracts/token/ERC20/SafeERC20.sol
429 |
430 |
431 | // pragma solidity >=0.6.0 <0.8.0;
432 |
433 | // import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
434 | // import "@openzeppelin/contracts/math/SafeMath.sol";
435 | // import "@openzeppelin/contracts/utils/Address.sol";
436 |
437 | /**
438 | * @title SafeERC20
439 | * @dev Wrappers around ERC20 operations that throw on failure (when the token
440 | * contract returns false). Tokens that return no value (and instead revert or
441 | * throw on failure) are also supported, non-reverting calls are assumed to be
442 | * successful.
443 | * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
444 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
445 | */
446 | library SafeERC20 {
447 | using SafeMath for uint256;
448 | using Address for address;
449 |
450 | function safeTransfer(IERC20 token, address to, uint256 value) internal {
451 | _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
452 | }
453 |
454 | function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
455 | _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
456 | }
457 |
458 | /**
459 | * @dev Deprecated. This function has issues similar to the ones found in
460 | * {IERC20-approve}, and its usage is discouraged.
461 | *
462 | * Whenever possible, use {safeIncreaseAllowance} and
463 | * {safeDecreaseAllowance} instead.
464 | */
465 | function safeApprove(IERC20 token, address spender, uint256 value) internal {
466 | // safeApprove should only be called when setting an initial allowance,
467 | // or when resetting it to zero. To increase and decrease it, use
468 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
469 | // solhint-disable-next-line max-line-length
470 | require((value == 0) || (token.allowance(address(this), spender) == 0),
471 | "SafeERC20: approve from non-zero to non-zero allowance"
472 | );
473 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
474 | }
475 |
476 | function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
477 | uint256 newAllowance = token.allowance(address(this), spender).add(value);
478 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
479 | }
480 |
481 | function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
482 | uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
483 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
484 | }
485 |
486 | /**
487 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
488 | * on the return value: the return value is optional (but if data is returned, it must not be false).
489 | * @param token The token targeted by the call.
490 | * @param data The call data (encoded using abi.encode or one of its variants).
491 | */
492 | function _callOptionalReturn(IERC20 token, bytes memory data) private {
493 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
494 | // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
495 | // the target address contains contract code and also asserts for success in the low-level call.
496 |
497 | bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
498 | if (returndata.length > 0) { // Return data is optional
499 | // solhint-disable-next-line max-line-length
500 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
501 | }
502 | }
503 | }
504 |
505 |
506 | // Dependency file: @openzeppelin/contracts/utils/ReentrancyGuard.sol
507 |
508 |
509 | // pragma solidity >=0.6.0 <0.8.0;
510 |
511 | /**
512 | * @dev Contract module that helps prevent reentrant calls to a function.
513 | *
514 | * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
515 | * available, which can be applied to functions to make sure there are no nested
516 | * (reentrant) calls to them.
517 | *
518 | * Note that because there is a single `nonReentrant` guard, functions marked as
519 | * `nonReentrant` may not call one another. This can be worked around by making
520 | * those functions `private`, and then adding `external` `nonReentrant` entry
521 | * points to them.
522 | *
523 | * TIP: If you would like to learn more about reentrancy and alternative ways
524 | * to protect against it, check out our blog post
525 | * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
526 | */
527 | abstract contract ReentrancyGuard {
528 | // Booleans are more expensive than uint256 or any type that takes up a full
529 | // word because each write operation emits an extra SLOAD to first read the
530 | // slot's contents, replace the bits taken up by the boolean, and then write
531 | // back. This is the compiler's defense against contract upgrades and
532 | // pointer aliasing, and it cannot be disabled.
533 |
534 | // The values being non-zero value makes deployment a bit more expensive,
535 | // but in exchange the refund on every call to nonReentrant will be lower in
536 | // amount. Since refunds are capped to a percentage of the total
537 | // transaction's gas, it is best to keep them low in cases like this one, to
538 | // increase the likelihood of the full refund coming into effect.
539 | uint256 private constant _NOT_ENTERED = 1;
540 | uint256 private constant _ENTERED = 2;
541 |
542 | uint256 private _status;
543 |
544 | constructor () internal {
545 | _status = _NOT_ENTERED;
546 | }
547 |
548 | /**
549 | * @dev Prevents a contract from calling itself, directly or indirectly.
550 | * Calling a `nonReentrant` function from another `nonReentrant`
551 | * function is not supported. It is possible to prevent this from happening
552 | * by making the `nonReentrant` function external, and make it call a
553 | * `private` function that does the actual work.
554 | */
555 | modifier nonReentrant() {
556 | // On the first call to nonReentrant, _notEntered will be true
557 | require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
558 |
559 | // Any calls to nonReentrant after this point will fail
560 | _status = _ENTERED;
561 |
562 | _;
563 |
564 | // By storing the original value once again, a refund is triggered (see
565 | // https://eips.ethereum.org/EIPS/eip-2200)
566 | _status = _NOT_ENTERED;
567 | }
568 | }
569 |
570 |
571 | // Dependency file: @openzeppelin/contracts/GSN/Context.sol
572 |
573 |
574 | // pragma solidity >=0.6.0 <0.8.0;
575 |
576 | /*
577 | * @dev Provides information about the current execution context, including the
578 | * sender of the transaction and its data. While these are generally available
579 | * via msg.sender and msg.data, they should not be accessed in such a direct
580 | * manner, since when dealing with GSN meta-transactions the account sending and
581 | * paying for execution may not be the actual sender (as far as an application
582 | * is concerned).
583 | *
584 | * This contract is only required for intermediate, library-like contracts.
585 | */
586 | abstract contract Context {
587 | function _msgSender() internal view virtual returns (address payable) {
588 | return msg.sender;
589 | }
590 |
591 | function _msgData() internal view virtual returns (bytes memory) {
592 | this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
593 | return msg.data;
594 | }
595 | }
596 |
597 |
598 | // Dependency file: @openzeppelin/contracts/access/Ownable.sol
599 |
600 |
601 | // pragma solidity >=0.6.0 <0.8.0;
602 |
603 | // import "@openzeppelin/contracts/GSN/Context.sol";
604 | /**
605 | * @dev Contract module which provides a basic access control mechanism, where
606 | * there is an account (an owner) that can be granted exclusive access to
607 | * specific functions.
608 | *
609 | * By default, the owner account will be the one that deploys the contract. This
610 | * can later be changed with {transferOwnership}.
611 | *
612 | * This module is used through inheritance. It will make available the modifier
613 | * `onlyOwner`, which can be applied to your functions to restrict their use to
614 | * the owner.
615 | */
616 | abstract contract Ownable is Context {
617 | address private _owner;
618 |
619 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
620 |
621 | /**
622 | * @dev Initializes the contract setting the deployer as the initial owner.
623 | */
624 | constructor () internal {
625 | address msgSender = _msgSender();
626 | _owner = msgSender;
627 | emit OwnershipTransferred(address(0), msgSender);
628 | }
629 |
630 | /**
631 | * @dev Returns the address of the current owner.
632 | */
633 | function owner() public view returns (address) {
634 | return _owner;
635 | }
636 |
637 | /**
638 | * @dev Throws if called by any account other than the owner.
639 | */
640 | modifier onlyOwner() {
641 | require(_owner == _msgSender(), "Ownable: caller is not the owner");
642 | _;
643 | }
644 |
645 | /**
646 | * @dev Leaves the contract without owner. It will not be possible to call
647 | * `onlyOwner` functions anymore. Can only be called by the current owner.
648 | *
649 | * NOTE: Renouncing ownership will leave the contract without an owner,
650 | * thereby removing any functionality that is only available to the owner.
651 | */
652 | function renounceOwnership() public virtual onlyOwner {
653 | emit OwnershipTransferred(_owner, address(0));
654 | _owner = address(0);
655 | }
656 |
657 | /**
658 | * @dev Transfers ownership of the contract to a new account (`newOwner`).
659 | * Can only be called by the current owner.
660 | */
661 | function transferOwnership(address newOwner) public virtual onlyOwner {
662 | require(newOwner != address(0), "Ownable: new owner is the zero address");
663 | emit OwnershipTransferred(_owner, newOwner);
664 | _owner = newOwner;
665 | }
666 | }
667 |
668 |
669 | // Root file: contracts/DePayLiquidityStaking.sol
670 |
671 |
672 | pragma solidity >=0.7.5 <0.8.0;
673 |
674 | // import 'contracts/interfaces/IUniswapV2Pair.sol';
675 |
676 | // import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
677 | // import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
678 | // import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
679 | // import "@openzeppelin/contracts/access/Ownable.sol";
680 | // import "@openzeppelin/contracts/math/SafeMath.sol";
681 |
682 | contract DePayLiquidityStaking is Ownable, ReentrancyGuard {
683 |
684 | using SafeMath for uint256;
685 | using SafeERC20 for IERC20;
686 |
687 | // Epoch time when staking starts: People are allowed to stake
688 | uint256 public startTime;
689 |
690 | // Epoch time when staking has been closed: People are not allowed to stake anymore
691 | uint256 public closeTime;
692 |
693 | // Epoch time when staking releases staked liquidity + rewards: People can withdrawal
694 | uint256 public releaseTime;
695 |
696 | // Total amount of staking rewards available
697 | uint256 public rewardsAmount;
698 |
699 | // Percentage Yield, will be divided by 100, e.g. set 80 for 80%
700 | uint256 public percentageYield;
701 |
702 | // Total amount of already allocated staking rewards
703 | uint256 public allocatedStakingRewards;
704 |
705 | // Address of the Uniswap liquidity pair (token)
706 | IUniswapV2Pair public liquidityToken;
707 |
708 | // Address of the token used for rewards
709 | IERC20 public token;
710 |
711 | // Indicating if unstaking early is allowed or not
712 | // This is used to upgrade liquidity to uniswap v3
713 | bool public unstakeEarlyAllowed;
714 |
715 | // Stores all rewards per address
716 | mapping (address => uint256) public rewardsPerAddress;
717 |
718 | // Stores all amounts of staked liquidity tokens per address
719 | mapping (address => uint256) public stakedLiquidityTokenPerAddress;
720 |
721 | // Token Reserve On initialization, used to calculate rewards upon staking
722 | uint256 public tokenReserveOnInit;
723 |
724 | // Liquidity Token Total Supply on initialization, used to calculate rewards upon staking
725 | uint256 public liquidityTokenTotalSupplyOnInit;
726 |
727 | modifier onlyUnstarted() {
728 | require(
729 | startTime == 0 || block.timestamp < startTime,
730 | "Staking has already started!"
731 | );
732 | _;
733 | }
734 |
735 | modifier onlyStarted() {
736 | require(block.timestamp > startTime, "Staking has not yet started!");
737 | _;
738 | }
739 |
740 | modifier onlyUnclosed() {
741 | require(block.timestamp < closeTime, "Staking has been closed!");
742 | _;
743 | }
744 |
745 | modifier onlyReleasable() {
746 | require(block.timestamp > releaseTime, "Staking is not releasable yet!");
747 | _;
748 | }
749 |
750 | modifier onlyUnstakeEarly() {
751 | require(unstakeEarlyAllowed, "Unstaking early not allowed!");
752 | _;
753 | }
754 |
755 | modifier onlyDistributedRewards(){
756 | require(allocatedStakingRewards == 0, 'Rewards were not distributed yet!');
757 | _;
758 | }
759 |
760 | function init(
761 | uint256 _startTime,
762 | uint256 _closeTime,
763 | uint256 _releaseTime,
764 | uint256 _percentageYield,
765 | address _liquidityToken,
766 | address _token
767 | ) external onlyOwner onlyUnstarted returns(bool) {
768 | require(isContract(_token), '_token address needs to be a contract!');
769 | require(isContract(_liquidityToken), '_liquidityToken address needs to be a contract!');
770 | require(_startTime < _closeTime && _closeTime < _releaseTime, '_startTime needs to be before _closeTime needs to be before _releaseTime!');
771 |
772 | startTime = _startTime;
773 | closeTime = _closeTime;
774 | releaseTime = _releaseTime;
775 | percentageYield = _percentageYield;
776 | liquidityToken = IUniswapV2Pair(_liquidityToken);
777 | token = IERC20(_token);
778 | rewardsAmount = token.balanceOf(address(this));
779 |
780 | require(liquidityToken.token0() == address(token), 'Rewards must be calculated based on the reward token address!');
781 | (tokenReserveOnInit,,) = liquidityToken.getReserves();
782 | liquidityTokenTotalSupplyOnInit = liquidityToken.totalSupply();
783 |
784 | return true;
785 | }
786 |
787 | function stake(
788 | uint256 stakedLiquidityTokenAmount
789 | ) external onlyStarted onlyUnclosed nonReentrant returns(bool) {
790 | require(
791 | liquidityToken.transferFrom(msg.sender, address(this), stakedLiquidityTokenAmount),
792 | 'Depositing liquidity token failed!'
793 | );
794 |
795 | uint256 rewards = stakedLiquidityTokenAmount
796 | .mul(tokenReserveOnInit)
797 | .div(liquidityTokenTotalSupplyOnInit)
798 | .mul(percentageYield)
799 | .div(100);
800 |
801 | rewardsPerAddress[msg.sender] = rewardsPerAddress[msg.sender].add(rewards);
802 | stakedLiquidityTokenPerAddress[msg.sender] = stakedLiquidityTokenPerAddress[msg.sender].add(stakedLiquidityTokenAmount);
803 |
804 | allocatedStakingRewards = allocatedStakingRewards.add(rewards);
805 | require(allocatedStakingRewards <= rewardsAmount, 'Staking overflows rewards!');
806 |
807 | return true;
808 | }
809 |
810 | function payableOwner() view private returns(address payable) {
811 | return payable(owner());
812 | }
813 |
814 | function withdraw(
815 | address tokenAddress,
816 | uint amount
817 | ) external onlyOwner nonReentrant returns(bool) {
818 | require(tokenAddress != address(liquidityToken), 'Not allowed to withdrawal liquidity tokens!');
819 |
820 | if(tokenAddress == address(token)) {
821 | require(
822 | allocatedStakingRewards <= token.balanceOf(address(this)).sub(amount),
823 | 'Only unallocated staking rewards are allowed to be withdrawn for roll-over to next staking contract!'
824 | );
825 | rewardsAmount = rewardsAmount.sub(amount);
826 | }
827 |
828 | IERC20(tokenAddress).safeTransfer(payableOwner(), amount);
829 | return true;
830 | }
831 |
832 | function _unstakeLiquidity() private {
833 | uint256 liquidityTokenAmount = stakedLiquidityTokenPerAddress[msg.sender];
834 | stakedLiquidityTokenPerAddress[msg.sender] = 0;
835 | require(
836 | liquidityToken.transfer(msg.sender, liquidityTokenAmount),
837 | 'Unstaking liquidity token failed!'
838 | );
839 | }
840 |
841 | function _unstakeRewards() private {
842 | uint256 rewards = rewardsPerAddress[msg.sender];
843 | allocatedStakingRewards = allocatedStakingRewards.sub(rewards);
844 | rewardsPerAddress[msg.sender] = 0;
845 | require(
846 | token.transfer(msg.sender, rewards),
847 | 'Unstaking rewards failed!'
848 | );
849 | }
850 |
851 | function unstake() external onlyReleasable nonReentrant returns(bool) {
852 | _unstakeLiquidity();
853 | _unstakeRewards();
854 | return true;
855 | }
856 |
857 | function enableUnstakeEarly() external onlyOwner returns(bool) {
858 | unstakeEarlyAllowed = true;
859 | return true;
860 | }
861 |
862 | function unstakeEarly() external onlyUnstakeEarly nonReentrant returns(bool) {
863 | _unstakeLiquidity();
864 | allocatedStakingRewards = allocatedStakingRewards.sub(rewardsPerAddress[msg.sender]);
865 | rewardsPerAddress[msg.sender] = 0;
866 | return true;
867 | }
868 |
869 | function isContract(address account) internal view returns(bool) {
870 | // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
871 | // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
872 | // for accounts without code, i.e. `keccak256('')`
873 | bytes32 codehash;
874 | bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
875 | // solhint-disable-next-line no-inline-assembly
876 | assembly { codehash := extcodehash(account) }
877 | return (codehash != accountHash && codehash != 0x0);
878 | }
879 |
880 | function destroy() public onlyOwner onlyDistributedRewards returns(bool) {
881 | selfdestruct(payable(owner()));
882 | return true;
883 | }
884 | }
885 |
--------------------------------------------------------------------------------
/test/DePayLiquidityStaking.spec.ts:
--------------------------------------------------------------------------------
1 | import chai, { expect } from 'chai'
2 | import {
3 | Contract,
4 | providers,
5 | Wallet
6 | } from 'ethers'
7 | import {
8 | solidity,
9 | } from 'ethereum-waffle'
10 | import IERC20 from '../artifacts/@openzeppelin/contracts/token/ERC20/IERC20.sol/IERC20.json'
11 | import Token from '../artifacts/contracts/test/token.sol/Token.json'
12 | import TokenSafeTransfer from '../artifacts/contracts/test/token_safe_transfer.sol/TokenSafeTransfer.json'
13 | import UniswapV2Pair from '../artifacts/contracts/test/uniswap_v2_pair.sol/UniswapV2Pair.json'
14 | import IUniswapV2Pair from '../artifacts/contracts/interfaces/IUniswapV2Pair.sol/IUniswapV2Pair.json'
15 | import DePayLiquidityStaking from '../artifacts/contracts/DePayLiquidityStaking.sol/DePayLiquidityStaking.json'
16 | import IDePayLiquidityStaking from '../artifacts/contracts/interfaces/IDePayLiquidityStaking.sol/IDePayLiquidityStaking.json'
17 |
18 | const { waffle, ethers } = require("hardhat")
19 | const {
20 | provider,
21 | deployContract,
22 | deployMockContract,
23 | loadFixture,
24 | } = waffle
25 |
26 | chai.use(solidity)
27 |
28 | describe('DePayLiquidityStaking', () => {
29 |
30 | const [ownerWallet, otherWallet] = provider.getWallets()
31 | let now = () => Math.round(new Date().getTime() / 1000)
32 |
33 | async function fixture() {
34 | const contract = await deployContract(ownerWallet, DePayLiquidityStaking)
35 | const tokenContract = await deployMockContract(ownerWallet, IERC20.abi)
36 | const liquidityTokenContract = await deployMockContract(ownerWallet, IUniswapV2Pair.abi)
37 | return {
38 | contract,
39 | ownerWallet,
40 | otherWallet,
41 | tokenContract,
42 | liquidityTokenContract
43 | }
44 | }
45 |
46 | interface initParameters {
47 | contract: Contract,
48 | wallet: Wallet,
49 | liquidityTokenContract?: Contract,
50 | liquidityTokenContractAddress?: string,
51 | tokenContract?: Contract,
52 | tokenContractAddress?: string,
53 | rewardsBalance?: string,
54 | pairReserve0?: string,
55 | pairReserve1?: string,
56 | totalSupply?: string,
57 | percentageYield?: string,
58 | startTime?: number,
59 | closeTime?: number,
60 | releaseTime?: number
61 | }
62 |
63 | async function init({
64 | contract,
65 | wallet,
66 | liquidityTokenContract,
67 | liquidityTokenContractAddress,
68 | tokenContract,
69 | tokenContractAddress,
70 | rewardsBalance = '0',
71 | pairReserve0 = '100000000000000000000000',
72 | pairReserve1 = '2000000000000000000000',
73 | totalSupply = '4000000000000000000000',
74 | percentageYield = '100', // for 100%
75 | startTime = now() - 10,
76 | closeTime = now() + 2610000, // + 1 month
77 | releaseTime = now() + 31536000 // + 12 month
78 | }: initParameters) {
79 | if(tokenContract) { tokenContractAddress = tokenContract.address }
80 | if(tokenContract && tokenContract.mock) {
81 | await tokenContract.mock.balanceOf.returns(rewardsBalance)
82 | }
83 | if(liquidityTokenContract) { liquidityTokenContractAddress = liquidityTokenContract.address }
84 | if(liquidityTokenContract && liquidityTokenContract.mock) {
85 | await liquidityTokenContract.mock.getReserves.returns(pairReserve0, pairReserve1, now())
86 | await liquidityTokenContract.mock.totalSupply.returns(totalSupply)
87 | await liquidityTokenContract.mock.token0.returns(tokenContractAddress)
88 | }
89 | await contract.connect(wallet).init(
90 | startTime,
91 | closeTime,
92 | releaseTime,
93 | percentageYield,
94 | liquidityTokenContractAddress,
95 | tokenContractAddress
96 | )
97 | return {
98 | startTime,
99 | closeTime,
100 | releaseTime,
101 | percentageYield
102 | }
103 | }
104 |
105 | interface stakeParameters {
106 | contract: Contract,
107 | tokenContractAddress: string,
108 | liquidityTokenContract: Contract,
109 | wallet: Wallet,
110 | stakedLiquidityTokenAmount: string
111 | }
112 |
113 | async function stake({
114 | contract,
115 | tokenContractAddress,
116 | liquidityTokenContract,
117 | wallet,
118 | stakedLiquidityTokenAmount
119 | }: stakeParameters) {
120 | if(liquidityTokenContract.mock) {
121 | await liquidityTokenContract.mock.transferFrom.returns(true)
122 | }
123 | await contract.connect(wallet).stake(stakedLiquidityTokenAmount)
124 | }
125 |
126 | interface withdrawParameters {
127 | contract: Contract,
128 | wallet: Wallet,
129 | token: string,
130 | amount: string
131 | }
132 |
133 | async function withdraw({
134 | contract,
135 | wallet,
136 | token,
137 | amount
138 | }: withdrawParameters) {
139 | await contract.connect(wallet).withdraw(token, amount)
140 | }
141 |
142 | interface deployLiquidityTokenParameters {
143 | contract: Contract,
144 | stakedLiquidityTokenAmount: string,
145 | totalLiquidityTokenSupply: string,
146 | tokenContract: Contract,
147 | pairReserve0: string,
148 | pairReserve1?: string,
149 | }
150 |
151 | async function deployLiquidityToken({
152 | contract,
153 | stakedLiquidityTokenAmount,
154 | totalLiquidityTokenSupply,
155 | tokenContract,
156 | pairReserve0,
157 | pairReserve1='100000000000000000000000'
158 | }: deployLiquidityTokenParameters){
159 | const liquidityTokenContract = await deployContract(
160 | ownerWallet,
161 | UniswapV2Pair,
162 | [ // constructor
163 | totalLiquidityTokenSupply,
164 | tokenContract.address,
165 | pairReserve0,
166 | pairReserve1
167 | ]
168 | )
169 | await liquidityTokenContract.transfer(otherWallet.address, stakedLiquidityTokenAmount)
170 | await liquidityTokenContract.connect(otherWallet).approve(contract.address, stakedLiquidityTokenAmount)
171 | return liquidityTokenContract
172 | }
173 |
174 | interface unstakeParameters {
175 | contract: Contract,
176 | wallet: Wallet
177 | }
178 |
179 | async function unstake({
180 | contract,
181 | wallet,
182 | }: unstakeParameters) {
183 | await contract.connect(wallet).unstake()
184 | }
185 |
186 | interface enableUnstakeEarlyParameters {
187 | contract: Contract,
188 | wallet: Wallet
189 | }
190 |
191 | async function enableUnstakeEarly({
192 | contract,
193 | wallet,
194 | }: unstakeParameters) {
195 | await contract.connect(wallet).enableUnstakeEarly()
196 | }
197 |
198 | interface unstakeEarlyParameters {
199 | contract: Contract,
200 | wallet: Wallet
201 | }
202 |
203 | async function unstakeEarly({
204 | contract,
205 | wallet,
206 | }: unstakeParameters) {
207 | await contract.connect(wallet).unstakeEarly()
208 | }
209 |
210 | interface destroyParameters {
211 | contract: Contract,
212 | wallet: Wallet
213 | }
214 |
215 | async function destroy({
216 | contract,
217 | wallet,
218 | }: destroyParameters) {
219 | await contract.connect(wallet).destroy()
220 | }
221 |
222 | it('deploys contract successfully', async () => {
223 | await loadFixture(fixture)
224 | })
225 |
226 | it('has the same interface as IDePayLiquidityStaking', async () => {
227 | const { contract, ownerWallet } = await loadFixture(fixture)
228 | const interfaceContract = await deployMockContract(ownerWallet, IDePayLiquidityStaking.abi)
229 | let inheritedFragmentNames: string[] = ['OwnershipTransferred', 'transferOwnership', 'owner', 'renounceOwnership']
230 | let contractFragmentsWithoutInheritance = contract.interface.fragments.filter((fragment: any)=> inheritedFragmentNames.indexOf(fragment.name) < 0)
231 | expect(
232 | JSON.stringify(contractFragmentsWithoutInheritance)
233 | ).to.eq(
234 | JSON.stringify(interfaceContract.interface.fragments)
235 | )
236 | })
237 |
238 | it('sets deployer wallet as the contract owner', async () => {
239 | const {contract, ownerWallet} = await loadFixture(fixture)
240 | const owner = await contract.owner()
241 | expect(owner).to.equal(ownerWallet.address)
242 | })
243 |
244 | it('allows the owner to initialize the staking contract', async () => {
245 | const {
246 | contract,
247 | ownerWallet,
248 | liquidityTokenContract,
249 | tokenContract
250 | } = await loadFixture(fixture)
251 | let rewardsAmount = '900000000000000000000000'
252 | const {
253 | startTime,
254 | closeTime,
255 | releaseTime,
256 | percentageYield,
257 | } = await init({
258 | contract,
259 | wallet: ownerWallet,
260 | liquidityTokenContract,
261 | tokenContract,
262 | rewardsBalance: rewardsAmount
263 | })
264 | expect(await contract.startTime()).to.eq(startTime)
265 | expect(await contract.closeTime()).to.eq(closeTime)
266 | expect(await contract.releaseTime()).to.eq(releaseTime)
267 | expect(await contract.percentageYield()).to.eq(percentageYield)
268 | expect(await contract.rewardsAmount()).to.eq(rewardsAmount)
269 | expect(await contract.liquidityToken()).to.eq(liquidityTokenContract.address)
270 | expect(await contract.token()).to.eq(tokenContract.address)
271 | })
272 |
273 | it('fails when initializing with _closeTime < _startTime', async () => {
274 | const {
275 | contract,
276 | ownerWallet,
277 | otherWallet,
278 | liquidityTokenContract,
279 | tokenContract
280 | } = await loadFixture(fixture)
281 | await expect(
282 | init({
283 | contract,
284 | wallet: ownerWallet,
285 | liquidityTokenContract,
286 | tokenContract: tokenContract,
287 | startTime: now(),
288 | closeTime: now() - 100,
289 | rewardsBalance: '900000000000000000000000'
290 | })
291 | ).to.be.revertedWith(
292 | 'VM Exception while processing transaction: revert _startTime needs to be before _closeTime needs to be before _releaseTime!'
293 | )
294 | })
295 |
296 | it('fails when initializing with _releaseTime < _closeTime', async () => {
297 | const {
298 | contract,
299 | ownerWallet,
300 | otherWallet,
301 | liquidityTokenContract,
302 | tokenContract
303 | } = await loadFixture(fixture)
304 | await expect(
305 | init({
306 | contract,
307 | wallet: ownerWallet,
308 | liquidityTokenContract,
309 | tokenContract: tokenContract,
310 | startTime: now(),
311 | closeTime: now() + 100,
312 | releaseTime: now(),
313 | rewardsBalance: '900000000000000000000000'
314 | })
315 | ).to.be.revertedWith(
316 | 'VM Exception while processing transaction: revert _startTime needs to be before _closeTime needs to be before _releaseTime!'
317 | )
318 | })
319 |
320 | it('fails when initializing with a wallet address as _token address', async () => {
321 | const {
322 | contract,
323 | ownerWallet,
324 | otherWallet,
325 | liquidityTokenContract,
326 | tokenContract
327 | } = await loadFixture(fixture)
328 | await expect(
329 | init({
330 | contract,
331 | wallet: ownerWallet,
332 | liquidityTokenContract,
333 | tokenContractAddress: otherWallet.address,
334 | rewardsBalance: '900000000000000000000000'
335 | })
336 | ).to.be.revertedWith(
337 | 'VM Exception while processing transaction: revert _token address needs to be a contract!'
338 | )
339 | })
340 |
341 | it('fails when initializing with a wallet address as _liquidityToken address', async () => {
342 | const {
343 | contract,
344 | ownerWallet,
345 | otherWallet,
346 | liquidityTokenContract,
347 | tokenContract
348 | } = await loadFixture(fixture)
349 | await expect(
350 | init({
351 | contract,
352 | wallet: ownerWallet,
353 | tokenContract,
354 | liquidityTokenContractAddress: otherWallet.address,
355 | rewardsBalance: '900000000000000000000000'
356 | })
357 | ).to.be.revertedWith(
358 | 'VM Exception while processing transaction: revert _liquidityToken address needs to be a contract!'
359 | )
360 | })
361 |
362 | it('prohibits other wallets but the owner to initialize the staking contract', async () => {
363 | const {
364 | contract,
365 | otherWallet,
366 | tokenContract,
367 | liquidityTokenContract
368 | } = await loadFixture(fixture)
369 | await expect(
370 | init({
371 | contract,
372 | wallet: otherWallet,
373 | tokenContract,
374 | liquidityTokenContract
375 | })
376 | ).to.be.revertedWith(
377 | 'VM Exception while processing transaction: revert Ownable: caller is not the owner'
378 | )
379 | })
380 |
381 | it('prohibits to initialize the staking contract again, when its already started', async () => {
382 | const {
383 | contract,
384 | ownerWallet,
385 | tokenContract,
386 | liquidityTokenContract
387 | } = await loadFixture(fixture)
388 | async function _init() {
389 | await init({
390 | contract,
391 | wallet: ownerWallet,
392 | tokenContract,
393 | liquidityTokenContract,
394 | rewardsBalance: '900000000000000000000000'
395 | })
396 | }
397 | await _init() // first time
398 | await expect(
399 | _init() // second time
400 | ).to.be.revertedWith(
401 | 'VM Exception while processing transaction: revert Staking has already started!'
402 | )
403 | })
404 |
405 | it('allows to stake liquidity when staking started', async () => {
406 | const {
407 | contract,
408 | ownerWallet,
409 | otherWallet,
410 | tokenContract,
411 | liquidityTokenContract
412 | } = await loadFixture(fixture)
413 | await init({
414 | contract,
415 | wallet: ownerWallet,
416 | tokenContract,
417 | liquidityTokenContract,
418 | percentageYield: '100',
419 | rewardsBalance: '900000000000000000000000',
420 | pairReserve0: '100000000000000000000000',
421 | totalSupply: '4000000000000000000000'
422 | })
423 | const stakedLiquidityTokenAmount = '2000000000000000000000'
424 | await stake({
425 | contract,
426 | tokenContractAddress: tokenContract.address,
427 | liquidityTokenContract,
428 | wallet: otherWallet,
429 | stakedLiquidityTokenAmount
430 | })
431 | expect(await contract.rewardsPerAddress(otherWallet.address)).to.eq('50000000000000000000000')
432 | expect(await contract.stakedLiquidityTokenPerAddress(otherWallet.address)).to.eq(stakedLiquidityTokenAmount)
433 | expect(await contract.allocatedStakingRewards()).to.eq('50000000000000000000000')
434 | })
435 |
436 | it('pays less rewards when setup with lower yield reward', async () => {
437 | const {
438 | contract,
439 | ownerWallet,
440 | otherWallet,
441 | liquidityTokenContract,
442 | tokenContract
443 | } = await loadFixture(fixture)
444 | await init({
445 | contract,
446 | wallet: ownerWallet,
447 | tokenContract,
448 | liquidityTokenContract,
449 | percentageYield: '80',
450 | rewardsBalance: '900000000000000000000000',
451 | pairReserve0: '100000000000000000000000',
452 | totalSupply: '4000000000000000000000'
453 | })
454 | const stakedLiquidityTokenAmount = '2000000000000000000000'
455 | await stake({
456 | contract,
457 | tokenContractAddress: tokenContract.address,
458 | liquidityTokenContract,
459 | wallet: otherWallet,
460 | stakedLiquidityTokenAmount
461 | })
462 | expect(await contract.rewardsPerAddress(otherWallet.address)).to.eq('40000000000000000000000')
463 | expect(await contract.stakedLiquidityTokenPerAddress(otherWallet.address)).to.eq(stakedLiquidityTokenAmount)
464 | expect(await contract.allocatedStakingRewards()).to.eq('40000000000000000000000')
465 | })
466 |
467 | it('prohibits to stake liquidity when staking did not start yet', async () => {
468 | const {
469 | contract,
470 | ownerWallet,
471 | otherWallet,
472 | liquidityTokenContract,
473 | tokenContract
474 | } = await loadFixture(fixture)
475 | await init({
476 | contract,
477 | wallet: ownerWallet,
478 | tokenContract,
479 | liquidityTokenContract,
480 | rewardsBalance: '900000000000000000000000',
481 | startTime: now() + 86400
482 | })
483 | await expect(contract.connect(otherWallet).stake('2157166313861058934633')).to.be.revertedWith(
484 | 'VM Exception while processing transaction: revert Staking has not yet started!'
485 | )
486 | })
487 |
488 | it('fails when trying to stake more than rewards left', async () => {
489 | const {
490 | contract,
491 | ownerWallet,
492 | otherWallet,
493 | liquidityTokenContract,
494 | tokenContract
495 | } = await loadFixture(fixture)
496 | await init({
497 | contract,
498 | wallet: ownerWallet,
499 | tokenContract,
500 | liquidityTokenContract,
501 | percentageYield: '100',
502 | rewardsBalance: '100000000000000000000000',
503 | pairReserve0: '100000000000000000000000',
504 | totalSupply: '4000000000000000000000'
505 | })
506 | const stakedLiquidityTokenAmount = '2000000000000000000000'
507 | await stake({
508 | contract,
509 | tokenContractAddress: tokenContract.address,
510 | liquidityTokenContract,
511 | wallet: otherWallet,
512 | stakedLiquidityTokenAmount
513 | })
514 | await expect(
515 | stake({
516 | contract,
517 | tokenContractAddress: tokenContract.address,
518 | liquidityTokenContract,
519 | wallet: otherWallet,
520 | stakedLiquidityTokenAmount: '3000000000000000000000'
521 | })
522 | ).to.be.revertedWith(
523 | 'VM Exception while processing transaction: revert Staking overflows rewards!'
524 | )
525 | expect(await contract.allocatedStakingRewards()).to.eq('50000000000000000000000')
526 | })
527 |
528 | it('fails when the reward token does not equal the first token in the liquidty pair used to calculate rewards', async () => {
529 | const {
530 | contract,
531 | ownerWallet,
532 | otherWallet,
533 | tokenContract,
534 | liquidityTokenContract
535 | } = await loadFixture(fixture)
536 | await liquidityTokenContract.mock.getReserves.returns('100000000000000000000000', '200000000000000000000000', now())
537 | await liquidityTokenContract.mock.totalSupply.returns('4000000000000000000000')
538 | await liquidityTokenContract.mock.token0.returns(ownerWallet.address)
539 | await expect(
540 | init({
541 | contract,
542 | wallet: ownerWallet,
543 | tokenContract,
544 | percentageYield: '100',
545 | rewardsBalance: '900000000000000000000000',
546 | tokenContractAddress: tokenContract.address,
547 | liquidityTokenContractAddress: liquidityTokenContract.address
548 | })
549 | ).to.be.revertedWith(
550 | 'VM Exception while processing transaction: revert Rewards must be calculated based on the reward token address!'
551 | )
552 | })
553 |
554 | it('fails when trying to stake after staking has been closed', async () => {
555 | const {
556 | contract,
557 | ownerWallet,
558 | otherWallet,
559 | tokenContract,
560 | liquidityTokenContract
561 | } = await loadFixture(fixture)
562 | await init({
563 | contract,
564 | wallet: ownerWallet,
565 | liquidityTokenContract,
566 | tokenContract,
567 | rewardsBalance: '900000000000000000000000',
568 | closeTime: now(),
569 | pairReserve0: '99425305856642687813605',
570 | totalSupply: '4314332627722117869266'
571 | })
572 | const stakedLiquidityTokenAmount = '2157166313861058934633'
573 | await expect(
574 | stake({
575 | contract,
576 | tokenContractAddress: tokenContract.address,
577 | liquidityTokenContract,
578 | wallet: otherWallet,
579 | stakedLiquidityTokenAmount: '2157166313861058934634'
580 | })
581 | ).to.be.revertedWith(
582 | 'VM Exception while processing transaction: revert Staking has been closed!'
583 | )
584 | })
585 |
586 | it('allows contract owner to withdraw tokens which ended up in the contract accidentally', async () => {
587 | const {
588 | contract,
589 | ownerWallet,
590 | otherWallet,
591 | tokenContract,
592 | liquidityTokenContract
593 | } = await loadFixture(fixture)
594 | await init({
595 | contract,
596 | wallet: ownerWallet,
597 | tokenContract,
598 | liquidityTokenContract,
599 | rewardsBalance: '900000000000000000000000'
600 | })
601 | const amount = '1000000000000000000'
602 | const anotherTokenContract = await deployContract(otherWallet, Token)
603 | await anotherTokenContract.transfer(contract.address, amount)
604 | await withdraw({
605 | contract,
606 | wallet: ownerWallet,
607 | token: anotherTokenContract.address,
608 | amount: amount
609 | })
610 | let balance = await anotherTokenContract.balanceOf(ownerWallet.address)
611 | expect(
612 | balance.toString()
613 | ).to.eq(amount)
614 | })
615 |
616 | it('does NOT allow to withdraw liquidity tokens, as they belong to the stakers', async () => {
617 | const {
618 | contract,
619 | ownerWallet,
620 | otherWallet,
621 | tokenContract,
622 | liquidityTokenContract
623 | } = await loadFixture(fixture)
624 | await init({
625 | contract,
626 | wallet: ownerWallet,
627 | tokenContract,
628 | liquidityTokenContract,
629 | rewardsBalance: '900000000000000000000000'
630 | })
631 | const amount = '1000000000000000000'
632 | await expect(
633 | withdraw({
634 | contract,
635 | wallet: ownerWallet,
636 | token: liquidityTokenContract.address,
637 | amount: amount
638 | })
639 | ).to.be.revertedWith(
640 | 'VM Exception while processing transaction: revert Not allowed to withdrawal liquidity tokens!'
641 | )
642 | })
643 |
644 | it('does allow to withdraw reward tokens if they have NOT been allocated to stakers yet', async () => {
645 | const {
646 | contract,
647 | ownerWallet,
648 | otherWallet,
649 | liquidityTokenContract
650 | } = await loadFixture(fixture)
651 | const tokenContract = await deployContract(otherWallet, Token)
652 | const amount = '1000000000000000000'
653 | await tokenContract.transfer(contract.address, amount)
654 | await init({
655 | contract,
656 | wallet: ownerWallet,
657 | tokenContract,
658 | liquidityTokenContract,
659 | rewardsBalance: amount
660 | })
661 | await expect(() =>
662 | withdraw({
663 | contract,
664 | wallet: ownerWallet,
665 | token: tokenContract.address,
666 | amount: amount
667 | })
668 | ).to.changeTokenBalance(tokenContract, ownerWallet, amount)
669 | expect(await contract.rewardsAmount()).to.eq('0')
670 | })
671 |
672 | it('uses safeTransfer when withdrawing tokens and does NOT decrease allocatedStakingRewards if withdrawing reward tokens fails', async () => {
673 | const {
674 | contract,
675 | ownerWallet,
676 | otherWallet,
677 | liquidityTokenContract
678 | } = await loadFixture(fixture)
679 | const tokenContract = await deployContract(otherWallet, TokenSafeTransfer)
680 | const amount = '1000000000000000000'
681 | await tokenContract.transfer(contract.address, amount)
682 | await init({
683 | contract,
684 | wallet: ownerWallet,
685 | tokenContract,
686 | liquidityTokenContract
687 | })
688 | await expect(
689 | withdraw({
690 | contract,
691 | wallet: ownerWallet,
692 | token: tokenContract.address,
693 | amount: amount
694 | })
695 | ).to.be.revertedWith(
696 | 'VM Exception while processing transaction: revert Token transfer failed!'
697 | )
698 | expect(await contract.rewardsAmount()).to.eq(amount)
699 | })
700 |
701 | it('does NOT allow to withdraw reward tokens if they have been allocated to stakers', async () => {
702 | const {
703 | contract,
704 | ownerWallet,
705 | otherWallet,
706 | liquidityTokenContract
707 | } = await loadFixture(fixture)
708 | const tokenContract = await deployContract(otherWallet, Token)
709 | const amount = '1000000000000000000'
710 | await tokenContract.transfer(contract.address, amount)
711 | await init({
712 | contract,
713 | wallet: ownerWallet,
714 | tokenContract,
715 | liquidityTokenContract,
716 | rewardsBalance: amount,
717 | pairReserve0: '994253058566426878',
718 | totalSupply: '4314332627722117869266'
719 | })
720 | await stake({
721 | contract,
722 | tokenContractAddress: tokenContract.address,
723 | liquidityTokenContract,
724 | wallet: otherWallet,
725 | stakedLiquidityTokenAmount: '2157166313861058934633'
726 | })
727 | await expect(
728 | withdraw({
729 | contract,
730 | wallet: ownerWallet,
731 | token: tokenContract.address,
732 | amount: amount
733 | })
734 | ).to.be.revertedWith(
735 | 'VM Exception while processing transaction: revert Only unallocated staking rewards are allowed to be withdrawn for roll-over to next staking contract!'
736 | )
737 | })
738 |
739 | it('does NOT allow other wallets to withdraw anything', async () => {
740 | const {
741 | contract,
742 | ownerWallet,
743 | otherWallet,
744 | liquidityTokenContract
745 | } = await loadFixture(fixture)
746 | const tokenContract = await deployContract(otherWallet, Token)
747 | const amount = '1000000000000000000'
748 | await tokenContract.transfer(contract.address, amount)
749 | await init({
750 | contract,
751 | wallet: ownerWallet,
752 | tokenContract,
753 | liquidityTokenContract,
754 | rewardsBalance: amount
755 | })
756 | await expect(
757 | withdraw({
758 | contract,
759 | wallet: otherWallet,
760 | token: tokenContract.address,
761 | amount: amount
762 | })
763 | ).to.be.revertedWith(
764 | 'VM Exception while processing transaction: revert Ownable: caller is not the owner'
765 | )
766 | })
767 |
768 | it('does allow to unstake if rewards are releasable', async () => {
769 | const {
770 | contract,
771 | ownerWallet,
772 | otherWallet
773 | } = await loadFixture(fixture)
774 | let rewardsAmount = '900000000000000000000000'
775 | const stakedLiquidityTokenAmount = '2000000000000000000000'
776 | const tokenContract = await deployContract(ownerWallet, Token)
777 | await tokenContract.transfer(contract.address, rewardsAmount)
778 | const totalLiquidityTokenSupply = '4000000000000000000000'
779 | const pairReserve0 = '100000000000000000000000'
780 | const liquidityTokenContract = await deployLiquidityToken({
781 | contract,
782 | stakedLiquidityTokenAmount,
783 | totalLiquidityTokenSupply,
784 | tokenContract,
785 | pairReserve0
786 | })
787 | await init({
788 | contract,
789 | wallet: ownerWallet,
790 | tokenContract,
791 | liquidityTokenContract,
792 | percentageYield: '100',
793 | closeTime: now()+300,
794 | releaseTime: now()+400,
795 | pairReserve0,
796 | totalSupply: totalLiquidityTokenSupply
797 | })
798 | await expect(() =>
799 | stake({
800 | contract,
801 | tokenContractAddress: tokenContract.address,
802 | liquidityTokenContract,
803 | wallet: otherWallet,
804 | stakedLiquidityTokenAmount
805 | })
806 | ).to.changeTokenBalance(liquidityTokenContract, otherWallet, '-2000000000000000000000')
807 | expect((await contract.allocatedStakingRewards()).toString()).to.eq('50000000000000000000000')
808 | await provider.send("evm_increaseTime", [500])
809 | await expect(() =>
810 | expect(() =>
811 | unstake({
812 | contract,
813 | wallet: otherWallet
814 | })
815 | ).to.changeTokenBalance(tokenContract, otherWallet, '50000000000000000000000')
816 | ).to.changeTokenBalance(liquidityTokenContract, otherWallet, '2000000000000000000000')
817 | expect((await contract.allocatedStakingRewards()).toString()).to.eq('0')
818 | })
819 |
820 | it('does not allow to unstake if rewards are not relesable yet', async () => {
821 | const {
822 | contract,
823 | ownerWallet,
824 | otherWallet,
825 | tokenContract,
826 | liquidityTokenContract
827 | } = await loadFixture(fixture)
828 | await init({
829 | contract,
830 | wallet: ownerWallet,
831 | tokenContract,
832 | liquidityTokenContract,
833 | percentageYield: '100',
834 | rewardsBalance: '900000000000000000000000',
835 | closeTime: now()+300,
836 | releaseTime: now()+400,
837 | pairReserve0: '100000000000000000000000',
838 | totalSupply: '4000000000000000000000'
839 | })
840 | await stake({
841 | contract,
842 | tokenContractAddress: tokenContract.address,
843 | liquidityTokenContract,
844 | wallet: otherWallet,
845 | stakedLiquidityTokenAmount: '2000000000000000000000'
846 | })
847 | await expect(
848 | unstake({
849 | contract,
850 | wallet: otherWallet
851 | })
852 | ).to.be.revertedWith(
853 | 'VM Exception while processing transaction: revert Staking is not releasable yet!'
854 | )
855 | expect(await contract.allocatedStakingRewards()).to.eq('50000000000000000000000')
856 | })
857 |
858 | it('allows the contract owner to enableUnstakeEarly', async () => {
859 | const {
860 | contract,
861 | ownerWallet,
862 | otherWallet
863 | } = await loadFixture(fixture)
864 | await enableUnstakeEarly({
865 | contract,
866 | wallet: ownerWallet
867 | })
868 | expect(await contract.unstakeEarlyAllowed()).to.eq(true)
869 | })
870 |
871 | it('does not allow others to enableUnstakeEarly', async () => {
872 | const {
873 | contract,
874 | ownerWallet,
875 | otherWallet
876 | } = await loadFixture(fixture)
877 | await expect(
878 | enableUnstakeEarly({
879 | contract,
880 | wallet: otherWallet
881 | })
882 | ).to.be.revertedWith(
883 | 'VM Exception while processing transaction: revert Ownable: caller is not the owner'
884 | )
885 | expect(await contract.unstakeEarlyAllowed()).to.eq(false)
886 | })
887 |
888 | it('allows to unstake early to migrate to uniswap v3', async () => {
889 | const {
890 | contract,
891 | ownerWallet,
892 | otherWallet
893 | } = await loadFixture(fixture)
894 | let rewardsAmount = '900000000000000000000000'
895 | const stakedLiquidityTokenAmount = '2000000000000000000000'
896 | const tokenContract = await deployContract(ownerWallet, Token)
897 | await tokenContract.transfer(contract.address, rewardsAmount)
898 | const totalLiquidityTokenSupply = '4000000000000000000000'
899 | const pairReserve0 = '100000000000000000000000'
900 | const liquidityTokenContract = await deployLiquidityToken({
901 | contract,
902 | stakedLiquidityTokenAmount,
903 | totalLiquidityTokenSupply,
904 | tokenContract,
905 | pairReserve0
906 | })
907 | await init({
908 | contract,
909 | wallet: ownerWallet,
910 | tokenContract,
911 | liquidityTokenContract,
912 | percentageYield: '100',
913 | closeTime: now()+300,
914 | releaseTime: now()+400,
915 | pairReserve0: pairReserve0,
916 | totalSupply: totalLiquidityTokenSupply
917 | })
918 | await expect(() =>
919 | stake({
920 | contract,
921 | tokenContractAddress: tokenContract.address,
922 | liquidityTokenContract,
923 | wallet: otherWallet,
924 | stakedLiquidityTokenAmount
925 | })
926 | ).to.changeTokenBalance(liquidityTokenContract, otherWallet, '-2000000000000000000000')
927 | expect((await contract.allocatedStakingRewards()).toString()).to.eq('50000000000000000000000')
928 | await enableUnstakeEarly({
929 | contract,
930 | wallet: ownerWallet
931 | })
932 | await expect(() =>
933 | expect(() =>
934 | unstakeEarly({
935 | contract,
936 | wallet: otherWallet
937 | })
938 | ).to.changeTokenBalance(tokenContract, otherWallet, '0') // no rewards for unstaking early
939 | ).to.changeTokenBalance(liquidityTokenContract, otherWallet, '2000000000000000000000')
940 | expect((await contract.allocatedStakingRewards()).toString()).to.eq('0')
941 | expect((await contract.rewardsPerAddress(otherWallet.address)).toString()).to.eq('0')
942 | })
943 |
944 | it('does not allow to unstake early to migrate to uniswap v3 if not enabled by the owner', async () => {
945 | const {
946 | contract,
947 | ownerWallet,
948 | otherWallet
949 | } = await loadFixture(fixture)
950 | let rewardsAmount = '900000000000000000000000'
951 | const stakedLiquidityTokenAmount = '2000000000000000000000'
952 | const tokenContract = await deployContract(ownerWallet, Token)
953 | await tokenContract.transfer(contract.address, rewardsAmount)
954 | const totalLiquidityTokenSupply = '4000000000000000000000'
955 | const pairReserve0 = '100000000000000000000000'
956 | const liquidityTokenContract = await deployLiquidityToken({
957 | contract,
958 | stakedLiquidityTokenAmount,
959 | totalLiquidityTokenSupply,
960 | tokenContract,
961 | pairReserve0
962 | })
963 | await init({
964 | contract,
965 | wallet: ownerWallet,
966 | tokenContract,
967 | liquidityTokenContract,
968 | percentageYield: '100',
969 | closeTime: now()+300,
970 | releaseTime: now()+400,
971 | pairReserve0: pairReserve0,
972 | totalSupply: totalLiquidityTokenSupply
973 | })
974 | await expect(() =>
975 | stake({
976 | contract,
977 | tokenContractAddress: tokenContract.address,
978 | liquidityTokenContract,
979 | wallet: otherWallet,
980 | stakedLiquidityTokenAmount
981 | })
982 | ).to.changeTokenBalance(liquidityTokenContract, otherWallet, '-2000000000000000000000')
983 | expect((await contract.allocatedStakingRewards()).toString()).to.eq('50000000000000000000000')
984 | await expect(
985 | unstakeEarly({
986 | contract,
987 | wallet: otherWallet
988 | })
989 | ).to.be.revertedWith(
990 | 'VM Exception while processing transaction: revert Unstaking early not allowed!'
991 | )
992 | expect((await contract.allocatedStakingRewards()).toString()).to.eq('50000000000000000000000')
993 | })
994 |
995 | it('does NOT allow to destroy the contract if you are not the owner', async () => {
996 | const {
997 | contract,
998 | ownerWallet,
999 | otherWallet
1000 | } = await loadFixture(fixture)
1001 | let rewardsAmount = '900000000000000000000000'
1002 | const stakedLiquidityTokenAmount = '2000000000000000000000'
1003 | const tokenContract = await deployContract(ownerWallet, Token)
1004 | await tokenContract.transfer(contract.address, rewardsAmount)
1005 | const totalLiquidityTokenSupply = '4000000000000000000000'
1006 | const pairReserve0 = '100000000000000000000000'
1007 | const liquidityTokenContract = await deployLiquidityToken({
1008 | contract,
1009 | stakedLiquidityTokenAmount,
1010 | totalLiquidityTokenSupply,
1011 | tokenContract,
1012 | pairReserve0
1013 | })
1014 | await init({
1015 | contract,
1016 | wallet: ownerWallet,
1017 | tokenContract,
1018 | liquidityTokenContract,
1019 | percentageYield: '100',
1020 | closeTime: now()+300,
1021 | releaseTime: now()+400,
1022 | pairReserve0: pairReserve0,
1023 | totalSupply: totalLiquidityTokenSupply
1024 | })
1025 | await expect(() =>
1026 | stake({
1027 | contract,
1028 | tokenContractAddress: tokenContract.address,
1029 | liquidityTokenContract,
1030 | wallet: otherWallet,
1031 | stakedLiquidityTokenAmount
1032 | })
1033 | ).to.changeTokenBalance(liquidityTokenContract, otherWallet, '-2000000000000000000000')
1034 | await expect(
1035 | destroy({
1036 | contract,
1037 | wallet: otherWallet
1038 | })
1039 | ).to.be.revertedWith(
1040 | 'VM Exception while processing transaction: revert Ownable: caller is not the owner'
1041 | )
1042 | })
1043 |
1044 | it('does NOT allow to destroy the contract if rewards still allocated', async () => {
1045 | const {
1046 | contract,
1047 | ownerWallet,
1048 | otherWallet
1049 | } = await loadFixture(fixture)
1050 | let rewardsAmount = '900000000000000000000000'
1051 | const stakedLiquidityTokenAmount = '2000000000000000000000'
1052 | const tokenContract = await deployContract(ownerWallet, Token)
1053 | await tokenContract.transfer(contract.address, rewardsAmount)
1054 | const totalLiquidityTokenSupply = '4000000000000000000000'
1055 | const pairReserve0 = '100000000000000000000000'
1056 | const liquidityTokenContract = await deployLiquidityToken({
1057 | contract,
1058 | stakedLiquidityTokenAmount,
1059 | totalLiquidityTokenSupply,
1060 | tokenContract,
1061 | pairReserve0
1062 | })
1063 | await init({
1064 | contract,
1065 | wallet: ownerWallet,
1066 | tokenContract,
1067 | liquidityTokenContract,
1068 | percentageYield: '100',
1069 | closeTime: now()+300,
1070 | releaseTime: now()+400,
1071 | pairReserve0: pairReserve0,
1072 | totalSupply: totalLiquidityTokenSupply
1073 | })
1074 | await expect(() =>
1075 | stake({
1076 | contract,
1077 | tokenContractAddress: tokenContract.address,
1078 | liquidityTokenContract,
1079 | wallet: otherWallet,
1080 | stakedLiquidityTokenAmount
1081 | })
1082 | ).to.changeTokenBalance(liquidityTokenContract, otherWallet, '-2000000000000000000000')
1083 | await expect(
1084 | destroy({
1085 | contract,
1086 | wallet: ownerWallet
1087 | })
1088 | ).to.be.revertedWith(
1089 | 'VM Exception while processing transaction: revert Rewards were not distributed yet!'
1090 | )
1091 | })
1092 |
1093 | it('allows to destroy the contract once everything has been unstaked and rewards have been payed out', async () => {
1094 | const {
1095 | contract,
1096 | ownerWallet,
1097 | otherWallet
1098 | } = await loadFixture(fixture)
1099 | let rewardsAmount = '900000000000000000000000'
1100 | const stakedLiquidityTokenAmount = '2000000000000000000000'
1101 | const tokenContract = await deployContract(ownerWallet, Token)
1102 | await tokenContract.transfer(contract.address, rewardsAmount)
1103 | const totalLiquidityTokenSupply = '4000000000000000000000'
1104 | const pairReserve0 = '100000000000000000000000'
1105 | const liquidityTokenContract = await deployLiquidityToken({
1106 | contract,
1107 | stakedLiquidityTokenAmount,
1108 | totalLiquidityTokenSupply,
1109 | tokenContract,
1110 | pairReserve0
1111 | })
1112 | await init({
1113 | contract,
1114 | wallet: ownerWallet,
1115 | tokenContract,
1116 | liquidityTokenContract,
1117 | percentageYield: '100',
1118 | closeTime: now()+300,
1119 | releaseTime: now()+400,
1120 | pairReserve0: pairReserve0,
1121 | totalSupply: totalLiquidityTokenSupply
1122 | })
1123 | await expect(() =>
1124 | stake({
1125 | contract,
1126 | tokenContractAddress: tokenContract.address,
1127 | liquidityTokenContract,
1128 | wallet: otherWallet,
1129 | stakedLiquidityTokenAmount
1130 | })
1131 | ).to.changeTokenBalance(liquidityTokenContract, otherWallet, '-2000000000000000000000')
1132 | await provider.send("evm_increaseTime", [500])
1133 | await unstake({
1134 | contract,
1135 | wallet: otherWallet
1136 | })
1137 | await destroy({
1138 | contract,
1139 | wallet: ownerWallet
1140 | })
1141 | })
1142 | })
1143 |
--------------------------------------------------------------------------------