├── .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 | --------------------------------------------------------------------------------