├── addresses ├── .gitkeep ├── bscTestnetAddresses.json ├── bscTestnetAddresses copy.json └── bscTestnetAddressesStage.json ├── .npmrc ├── .solhintignore ├── README.md ├── test ├── 3_PancakeStrategy.test.ts ├── use-forking │ ├── 4_PancakeStrategy.oldTest.ts │ └── 4_StableCoinStrategyCurve.test.ts └── 1_IncentiveVoting.test.ts ├── .npmignore ├── .eslintignore ├── contracts ├── mocks │ ├── MasterChefMock.sol │ ├── interfaces │ │ └── IERC20MintBurnable.sol │ ├── FakeERC20.sol │ ├── StrategyMock.sol │ └── PancakeRouterMock.sol ├── upgrades │ ├── IncentiveVotingV2_1.sol │ ├── IncentiveVotingV1_1.sol │ ├── IncentiveVotingV2.sol │ ├── PancakeStrategyV2.sol │ └── IncentiveVotingTemp.sol ├── interfaces │ ├── IExternalPool.sol │ ├── ITokenBonding.sol │ ├── IWBNB.sol │ ├── IIncentiveVoting.sol │ ├── IPancakeFactory.sol │ ├── IStrategy.sol │ ├── IFarming.sol │ ├── IStable.sol │ ├── IPancakeRouter02.sol │ ├── IPancakeSwapFarm.sol │ ├── IPancakePair.sol │ └── IPancakeRouter01.sol ├── MintERC20.sol ├── externalPools │ ├── ex.txt │ ├── ExternalEllipsis.sol │ └── ExternalJar.sol ├── ProxyUpgrades │ ├── ProxyAdmin.sol │ └── TransparentUpgradeableProxy.sol ├── CurveProxyForDeposit.sol ├── PancakeProxyForDeposit.sol ├── PancakeStrategy.sol ├── IncentiveVoting.sol ├── StableCoinStrategyCurve.sol ├── TokenBonding.sol └── Farming.sol ├── .solcover.js ├── .prettierignore ├── .env.example ├── .gitignore ├── .solhint.json ├── tsconfig.json ├── scripts ├── transferOwnership.ts ├── simple.ts ├── deployFarming.ts ├── deployPancakeStrategy.ts ├── deployTokenBonding.ts ├── upgrades │ ├── upgrade-incentiveVoting.ts │ └── upgrade-pancake-strategy.ts ├── deployCurveProxyForDeposit.ts ├── deployStableCoinStrategy.ts ├── apr.ts ├── apr1.ts ├── deployAllTestnet.ts ├── deployAllTestnetDev.ts └── deployAll.ts ├── .vscode └── settings.json ├── .prettierrc.json ├── .eslintrc.json ├── helpers ├── NetworkSnapshotter.ts └── utils.ts ├── package.json ├── hardhat.config.ts ├── DEVELOPING.md └── .openzeppelin └── unknown-31337.json /addresses/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=true -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FARMING-CONTRACTS 2 | -------------------------------------------------------------------------------- /test/3_PancakeStrategy.test.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | hardhat.config.ts 2 | scripts 3 | test 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage 5 | -------------------------------------------------------------------------------- /contracts/mocks/MasterChefMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ["mocks/FakeERC20.sol", "mocks/StrategyMock.sol"] 3 | } -------------------------------------------------------------------------------- /contracts/upgrades/IncentiveVotingV2_1.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; -------------------------------------------------------------------------------- /contracts/upgrades/IncentiveVotingV1_1.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage* 5 | gasReporterOutput.json 6 | typechain-types 7 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | BSC_API_KEY= 2 | DEPLOYER_PK= 3 | BSC_URL= 4 | START_TIME= 5 | BSC_TESTNET_URL= 6 | REPORT_GAS= 7 | FORKING_URL= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain-types 6 | 7 | #Hardhat files 8 | cache 9 | artifacts 10 | -------------------------------------------------------------------------------- /contracts/interfaces/IExternalPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | interface IExternalPool { 5 | function addReward(uint256 amount) external returns (bool); 6 | } 7 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "func-visibility": ["warn", { "ignoreConstructors": true }], 5 | "compiler-version": ["error", "^0.8.0"], 6 | "not-rely-on-time": "off", 7 | "reason-string": "off" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /contracts/interfaces/ITokenBonding.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | interface ITokenBonding { 5 | function userWeight(address _user) external view returns (uint256); 6 | 7 | function totalWeight() external view returns (uint256); 8 | } 9 | -------------------------------------------------------------------------------- /contracts/interfaces/IWBNB.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | 6 | interface IWBNB is IERC20 { 7 | function deposit() external payable; 8 | 9 | function withdraw(uint256 wad) external; 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "declaration": true 9 | }, 10 | "include": ["./scripts", "./test", "./helpers", "./typechain-types"], 11 | "files": ["./hardhat.config.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /contracts/interfaces/IIncentiveVoting.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | 6 | interface IIncentiveVoting { 7 | function getRewardsPerSecond(uint256 _pid, uint256 _week) external view returns (uint256); 8 | 9 | function startTime() external view returns (uint256); 10 | } 11 | -------------------------------------------------------------------------------- /scripts/transferOwnership.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | 3 | const strategyAddress = "0xe8baC331FaF2f65eA1db5425Cf3Ee9dB59C8e3cd"; 4 | const newOwnerAddress = "0x8d388136d578dCD791D081c6042284CED6d9B0c6"; 5 | 6 | const main = async () => { 7 | const strategy = await ethers.getContractAt("Ownable", strategyAddress); 8 | await strategy.transferOwnership(newOwnerAddress); 9 | }; 10 | 11 | main(); 12 | -------------------------------------------------------------------------------- /contracts/MintERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract MintERC20 is ERC20 { 7 | constructor() ERC20("MINT", "MT") {} 8 | 9 | function mint(address to, uint256 amount) external { 10 | _mint(to, amount); 11 | } 12 | 13 | function burn(address to, uint256 amount) external { 14 | _burn(to, amount); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripts/simple.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import { sleep, verifyContract } from "../helpers/utils"; 3 | 4 | const main = async () => { 5 | const MintERC20 = await ethers.getContractFactory("MintERC20"); 6 | const token = await MintERC20.deploy(); 7 | await token.deployed(); 8 | console.log("new implementation address ->", token.address); 9 | 10 | await sleep(16000); 11 | 12 | await verifyContract(token.address, []); 13 | }; 14 | 15 | main(); 16 | -------------------------------------------------------------------------------- /scripts/deployFarming.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import { sleep, verifyContract } from "../helpers/utils"; 3 | 4 | const main = async () => { 5 | const Farming = await ethers.getContractFactory("Farming"); 6 | const farming = await Farming.deploy(); 7 | await farming.deployed(); 8 | console.log("new implementation address ->", farming.address); 9 | 10 | await sleep(16000); 11 | 12 | await verifyContract(farming.address, []); 13 | }; 14 | 15 | main(); 16 | -------------------------------------------------------------------------------- /scripts/deployPancakeStrategy.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import { sleep, verifyContract } from "../helpers/utils"; 3 | 4 | const main = async () => { 5 | const Strategy = await ethers.getContractFactory("PancakeStrategyV2"); 6 | const strategy = await Strategy.deploy(); 7 | await strategy.deployed(); 8 | console.log("new implementation address ->", strategy.address); 9 | 10 | await sleep(16000); 11 | 12 | await verifyContract(strategy.address, []); 13 | }; 14 | 15 | main(); 16 | -------------------------------------------------------------------------------- /contracts/mocks/interfaces/IERC20MintBurnable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; 5 | 6 | interface IERC20MintBurnable is IERC20Metadata { 7 | function mint(address to, uint256 amount) external; 8 | 9 | function mintMe(uint256 amount) external; 10 | 11 | function burn(address to, uint256 amount) external; 12 | 13 | function burnMe(uint256 amount) external; 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | "**/Thumbs.db": true, 9 | "**/node_modules": true, 10 | "**/typechain-types": true, 11 | "**/cache": true 12 | }, 13 | "cSpell.words": [ 14 | "Coeff", 15 | "IWBNB", 16 | "MASTERCHEF", 17 | "pids", 18 | "setted", 19 | "Snapshotter", 20 | "Strat", 21 | "unbond", 22 | "Unbonding" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": false, 7 | "quoteProps": "as-needed", 8 | "trailingComma": "es5", 9 | "jsxSingleQuote": false, 10 | "bracketSpacing": true, 11 | "bracketSameLine": false, 12 | "arrowParens": "always", 13 | "rangeStart": 0, 14 | "proseWrap": "preserve", 15 | "htmlWhitespaceSensitivity": "css", 16 | "vueIndentScriptAndStyle": false, 17 | "endOfLine": "lf", 18 | "embeddedLanguageFormatting": "auto", 19 | "requirePragma": false, 20 | "insertPragma": false 21 | } 22 | -------------------------------------------------------------------------------- /addresses/bscTestnetAddresses.json: -------------------------------------------------------------------------------- 1 | { 2 | "hay": "0xf052cb891c3887acca8741859c6184794c482a8a", 3 | "incentiveVoting": "0x1385705b575B64d9e3F3967B4E354A5b193B34C5", 4 | "incentiveVotingImplementation": "0x4023afbdc5C22A5690776568Ef6c7093c7749F06", 5 | "farming": "0xb3cDc24E9499309f20968D26d2a9C29D723a3320", 6 | "farmingImplementation": "0x07237868FE2C92d79799D08D98809849A2061DA4", 7 | "pancakeProxyForDeposit": "0xB335a135D7F56389DADeF00F6Eb526b07631C3a5", 8 | "pancakeProxyForDepositImplementation": "0xDC0909c1b6055eE764F8A34735c49bfe0B783a60", 9 | "pancakeStrategy": "0x0A804DFd7a8e0E112eb6AeDc09c7F388210c52A1", 10 | "pancakeStrategyImplementation": "0x489496Ceec64FdA2dB3D60E3Aa2d1e0F79783186" 11 | } 12 | -------------------------------------------------------------------------------- /contracts/interfaces/IPancakeFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | interface IPancakeFactory { 5 | function feeTo() external view returns (address); 6 | 7 | function feeToSetter() external view returns (address); 8 | 9 | function getPair(address tokenA, address tokenB) external view returns (address pair); 10 | 11 | function allPairs(uint256) external view returns (address pair); 12 | 13 | function allPairsLength() external view returns (uint256); 14 | 15 | function createPair(address tokenA, address tokenB) external returns (address pair); 16 | 17 | function setFeeTo(address) external; 18 | 19 | function setFeeToSetter(address) external; 20 | } 21 | -------------------------------------------------------------------------------- /addresses/bscTestnetAddresses copy.json: -------------------------------------------------------------------------------- 1 | { 2 | "hay": "0xeEeb52943d6e3EAB97Fd99170d2415716d3B6DF3", 3 | "incentiveVoting": "0xc1b5476fc21fd351118Fa378f45199576eB5Cc63", 4 | "incentiveVotingImplementation": "0xD3eF1127BCec4de708880e78D3cE40bcAB6404b2", 5 | "farming": "0x9be9d1aff4DbC78f787D0Df1ECD775b01AeF1F6e", 6 | "farmingImplementation": "0xbdE27ec6D7CdD999eaA2a7D5CFDB467414e1997c", 7 | "pancakeProxyForDeposit": "0x77F94dbb32D06Fd836FB2b28Bdac147dDF7c486F", 8 | "pancakeProxyForDepositImplementation": "0xDC0909c1b6055eE764F8A34735c49bfe0B783a60", 9 | "pancakeStrategy": "0xED296ff56f5Ec8BA559781107795b7347cDD5186", 10 | "pancakeStrategyImplementation": "0x489496Ceec64FdA2dB3D60E3Aa2d1e0F79783186" 11 | } 12 | -------------------------------------------------------------------------------- /addresses/bscTestnetAddressesStage.json: -------------------------------------------------------------------------------- 1 | { 2 | "hay": "0x7adC9A28Fab850586dB99E7234EA2Eb7014950fA", 3 | "incentiveVoting": "0x6cD66ca25b90A739D270588543B9c36980444888", 4 | "incentiveVotingImplementation": "0xc50f1219d1e7FE6eCda53FC054715994dC9F5200", 5 | "farming": "0x43587B5bE016Ff1AD33fcE1eA98561cec559506C", 6 | "farmingImplementation": "0x9A85a1289E41c2DC7Bf2aA8003E603F7c3702FcC", 7 | "pancakeProxyForDeposit": "0x968e6C1614aBe4261B65cC298A1FadCdC2cC90b9", 8 | "pancakeProxyForDepositImplementation": "0x2d664490a50A0B2c610Ee4090920a0E86Af42BD2", 9 | "pancakeStrategy": "0x4426d4161E00CED5016432FcCC69867eb963a7CF", 10 | "pancakeStrategyImplementation": "0xfa18eB739310AB35fd8A6E02737220C3491b9Ab1" 11 | } 12 | -------------------------------------------------------------------------------- /contracts/interfaces/IStrategy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | // For interacting with our own strategy 5 | interface IStrategy { 6 | // Total want tokens managed by stratfegy 7 | function wantLockedTotal() external view returns (uint256); 8 | 9 | // Sum of all shares of users to wantLockedTotal 10 | function sharesTotal() external view returns (uint256); 11 | 12 | // Transfer want tokens autoFarm -> strategy 13 | function deposit(address _userAddress, uint256 _wantAmt) external returns (uint256); 14 | 15 | // Transfer want tokens strategy -> autoFarm 16 | function withdraw(address _userAddress, uint256 _wantAmt) external returns (uint256); 17 | 18 | function inCaseTokensGetStuck( 19 | address _token, 20 | uint256 _amount, 21 | address _to 22 | ) external; 23 | } 24 | -------------------------------------------------------------------------------- /contracts/mocks/FakeERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import { IERC20MintBurnable } from "./interfaces/IERC20MintBurnable.sol"; 6 | 7 | contract FakeERC20 is IERC20MintBurnable, ERC20 { 8 | // solhint-disable-next-line no-empty-blocks 9 | constructor(string memory name, string memory symbol) ERC20(name, symbol) {} 10 | 11 | function mint(address to, uint256 amount) external { 12 | _mint(to, amount); 13 | } 14 | 15 | function mintMe(uint256 amount) external { 16 | _mint(msg.sender, amount); 17 | } 18 | 19 | function burn(address to, uint256 amount) external { 20 | _burn(to, amount); 21 | } 22 | 23 | function burnMe(uint256 amount) external { 24 | _burn(msg.sender, amount); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /scripts/deployTokenBonding.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "ethers"; 2 | import { ethers, upgrades } from "hardhat"; 3 | import { verifyContract } from "../helpers/utils"; 4 | 5 | const ten = BigNumber.from(10); 6 | const tenPow18 = ten.pow(18); 7 | 8 | const HELIO = "0x1119022D7831430632c729AFF1F16FA23a1C8CfE"; 9 | 10 | const main = async () => { 11 | const TokenBondingFactory = await ethers.getContractFactory("TokenBonding"); 12 | const tokenBonding = await upgrades.deployProxy(TokenBondingFactory, [[HELIO], [tenPow18]]); 13 | await tokenBonding.deployed(); 14 | const impl = await upgrades.erc1967.getImplementationAddress(tokenBonding.address); 15 | console.log("TokenBonding proxy address is ->", tokenBonding.address); 16 | console.log("TokenBonding impl address is ->", impl); 17 | await verifyContract(impl, []); 18 | }; 19 | main(); 20 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "es2021": true, 5 | "mocha": true, 6 | "node": true 7 | }, 8 | "plugins": ["@typescript-eslint"], 9 | "extends": [ 10 | "standard", 11 | "plugin:prettier/recommended", 12 | "plugin:@typescript-eslint/eslint-recommended", 13 | "plugin:@typescript-eslint/recommended", 14 | "plugin:node/recommended", 15 | "prettier" 16 | ], 17 | "parser": "@typescript-eslint/parser", 18 | "parserOptions": { 19 | "ecmaVersion": 12, 20 | "project": "tsconfig.json" 21 | }, 22 | "settings": { 23 | "node": { 24 | "tryExtensions": [".js", ".json", ".node", ".ts"] 25 | } 26 | }, 27 | "rules": { 28 | "node/no-unsupported-features/es-syntax": ["error", { "ignores": ["modules"] }], 29 | "@typescript-eslint/no-explicit-any": "off" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /scripts/upgrades/upgrade-incentiveVoting.ts: -------------------------------------------------------------------------------- 1 | import { ethers, upgrades } from "hardhat"; 2 | import { verifyContract } from "../../helpers/utils"; 3 | 4 | const IncentiveVotingProxy = "0x6cD66ca25b90A739D270588543B9c36980444888"; 5 | 6 | const main = async () => { 7 | const IncentiveVotingFactory = await ethers.getContractFactory("IncentiveVotingV2"); 8 | const incentiveVoting = await IncentiveVotingFactory.deploy(); 9 | await incentiveVoting.deployed(); 10 | console.log("new implementation was deployed on ->", incentiveVoting.address); 11 | 12 | const proxyAdminAddress = await upgrades.erc1967.getAdminAddress(IncentiveVotingProxy); 13 | const proxyAdmin = await ethers.getContractAt("ProxyAdmin", proxyAdminAddress); 14 | await proxyAdmin.upgrade(IncentiveVotingProxy, incentiveVoting.address); 15 | await verifyContract(incentiveVoting.address, []); 16 | }; 17 | 18 | main(); 19 | -------------------------------------------------------------------------------- /contracts/externalPools/ex.txt: -------------------------------------------------------------------------------- 1 | monday 2 | rewards => 700 -> 7 days 3 | user1 => 300; 4 | user2 => 400; 5 | 6 | after 4 days 7 | 300 rewards. 8 | jar.replanish(1400); 9 | 1700 rewards 10 | 11 | Friday - Suday 12 | 6pm - 20 rewards 13 | jar.replanish(1400, true); 14 | 15 | 16 | week1 and week2 17 | 18 | addreward(week1); 19 | users will get on week2 20 | 21 | week1 - we will add rewards to the incentiveVoting contract 22 | and users will vote and choose pool shares 23 | on week2, monday ~00:00 we will call distributerewards() 24 | and it will distribute rewards through the pools 25 | 26 | 27 | vote for week2 - 50% internal pool, 20% ellipsis, and 30% jar - we have 100000 28 | on week3 - 50000 - internal, 20000 ellipsis, 30000 jar 29 | after 3 days someone calls replanish(0, true); 30 | 15000 rewards for jar 31 | 32 | replanish - on monday 00:00 with argument "true". 33 | 34 | week1 monday replanish(1000, true); 35 | after 7 + X days we are calling replanish(1000, false); 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /helpers/NetworkSnapshotter.ts: -------------------------------------------------------------------------------- 1 | import { network } from "hardhat"; 2 | 3 | class NetworkSnapshotter { 4 | snapshotId: number; 5 | snapshotIds: number[]; 6 | 7 | constructor() { 8 | this.snapshotId = 0; 9 | this.snapshotIds = []; 10 | } 11 | 12 | async revert() { 13 | await network.provider.send("evm_revert", [this.snapshotId]); 14 | return this.firstSnapshot(); 15 | } 16 | 17 | async firstSnapshot() { 18 | this.snapshotId = await network.provider.send("evm_snapshot", []); 19 | } 20 | 21 | async newSnapshot() { 22 | this.snapshotIds.push(this.snapshotId); 23 | this.snapshotId = await network.provider.send("evm_snapshot", []); 24 | } 25 | 26 | async revertLastSnapshot() { 27 | if (this.snapshotIds.length === 0) { 28 | throw new Error("there is no registered snapshot"); 29 | } 30 | const snapId = this.snapshotIds.pop() as number; 31 | this.snapshotId = snapId; 32 | return this.revert(); 33 | } 34 | } 35 | 36 | export default NetworkSnapshotter; 37 | -------------------------------------------------------------------------------- /contracts/interfaces/IFarming.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { IStrategy } from "./IStrategy.sol"; 5 | import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 6 | 7 | interface IFarming { 8 | function poolInfo(uint256 pid) 9 | external 10 | view 11 | returns ( 12 | IERC20Upgradeable, 13 | IStrategy, 14 | uint256, 15 | uint256, 16 | uint256 17 | ); 18 | 19 | function addPool( 20 | address token, 21 | address strategy, 22 | bool withUpdate 23 | ) external returns (uint256); 24 | 25 | function rewardToken() external returns (IERC20Upgradeable); 26 | 27 | function deposit( 28 | uint256 pid, 29 | uint256 wantAmt, 30 | bool claimRewards, 31 | address userAddress 32 | ) external returns (uint256); 33 | 34 | function withdraw( 35 | uint256 pid, 36 | uint256 wantAmt, 37 | bool claimRewards 38 | ) external returns (uint256); 39 | 40 | function claim(address user, uint256[] calldata pids) external returns (uint256); 41 | } 42 | -------------------------------------------------------------------------------- /contracts/externalPools/ExternalEllipsis.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { IExternalPool } from "../interfaces/IExternalPool.sol"; 5 | import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; 6 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 8 | 9 | interface IEllipsis { 10 | function notifyRewardAmount(address _rewardsToken, uint256 reward) external; 11 | } 12 | 13 | contract ExternalEllipsis is IExternalPool, Ownable { 14 | using SafeERC20 for IERC20; 15 | 16 | IEllipsis public ellipsis; 17 | IERC20 public token; 18 | 19 | constructor(address _ellipsis, address _token) { 20 | ellipsis = IEllipsis(_ellipsis); 21 | token = IERC20(_token); 22 | token.approve(_ellipsis, type(uint256).max); 23 | } 24 | 25 | function addReward(uint256 amount) external returns (bool) { 26 | token.safeTransferFrom(msg.sender, address(this), amount); 27 | ellipsis.notifyRewardAmount(address(token), amount); 28 | return true; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /contracts/externalPools/ExternalJar.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { IExternalPool } from "../interfaces/IExternalPool.sol"; 5 | import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; 6 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 8 | 9 | interface IJar { 10 | function replenish(uint256 wad, bool newSpread) external; 11 | } 12 | 13 | contract ExternalJar is IExternalPool, Ownable { 14 | using SafeERC20 for IERC20; 15 | 16 | IJar public jar; 17 | IERC20 public token; 18 | bool public newSpread; 19 | 20 | constructor(address _jar, address _token) { 21 | jar = IJar(_jar); 22 | token = IERC20(_token); 23 | token.approve(_jar, type(uint256).max); 24 | } 25 | 26 | function addReward(uint256 amount) external returns (bool) { 27 | token.safeTransferFrom(msg.sender, address(this), amount); 28 | jar.replenish(amount, newSpread); 29 | return true; 30 | } 31 | 32 | function setSpread(bool _newSpread) external onlyOwner { 33 | newSpread = _newSpread; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /scripts/upgrades/upgrade-pancake-strategy.ts: -------------------------------------------------------------------------------- 1 | import { ethers, upgrades } from "hardhat"; 2 | import { verifyContract } from "../../helpers/utils"; 3 | 4 | if (!process.env.PANCAKE_STRAT) { 5 | throw new Error("Pancake strategy address is not setted to .env"); 6 | } 7 | if (!process.env.MASTERCHEF_PID) { 8 | throw new Error("Masterchef pid is not setted to .env"); 9 | } 10 | 11 | const PANCAKE_STRAT = process.env.PANCAKE_STRAT; 12 | const MASTERCHEF_PID = process.env.MASTERCHEF_PID; 13 | 14 | const main = async () => { 15 | const PancakeStrategyV2Factory = await ethers.getContractFactory("PancakeStrategyV2"); 16 | console.log("upgrading the strategy"); 17 | const pancakeStratV2 = await upgrades.upgradeProxy(PANCAKE_STRAT, PancakeStrategyV2Factory); 18 | await pancakeStratV2.deployed(); 19 | console.log("Successfully upgraded!"); 20 | 21 | console.log("Set the pid for the Masterchef"); 22 | await pancakeStratV2.setPid(MASTERCHEF_PID); 23 | 24 | const pancakeStratV2Impl = await pancakeStratV2.erc1967.getImplementation(); 25 | await verifyContract(pancakeStratV2Impl, []); 26 | }; 27 | 28 | main() 29 | .then(() => { 30 | console.log("Success"); 31 | }) 32 | .catch((err) => { 33 | console.log(err); 34 | }); 35 | -------------------------------------------------------------------------------- /contracts/interfaces/IStable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | uint256 constant N_COINS = 2; 5 | 6 | interface IStable { 7 | function balances(uint256) external view returns (uint256); 8 | 9 | function A() external view returns (uint256); 10 | 11 | function get_virtual_price() external view returns (uint256); 12 | 13 | function calc_token_amount(uint256[N_COINS] memory amounts, bool deposit) 14 | external 15 | view 16 | returns (uint256); 17 | 18 | function add_liquidity(uint256[N_COINS] memory amounts, uint256 min_mint_amount) external; 19 | 20 | function get_dy( 21 | uint256 i, 22 | uint256 j, 23 | uint256 dx 24 | ) external view returns (uint256); 25 | 26 | function get_dy_underlying( 27 | uint256 i, 28 | uint256 j, 29 | uint256 dx 30 | ) external view returns (uint256); 31 | 32 | function exchange( 33 | uint256 i, 34 | uint256 j, 35 | uint256 dx, 36 | uint256 min_dy 37 | ) external; 38 | 39 | function remove_liquidity(uint256 _amount, uint256[N_COINS] memory min_amounts) external; 40 | 41 | function remove_liquidity_imbalance(uint256[N_COINS] memory amounts, uint256 max_burn_amount) 42 | external; 43 | 44 | function calc_withdraw_one_coin(uint256 _token_amount, uint256 i) external view returns (uint256); 45 | 46 | function remove_liquidity_one_coin( 47 | uint256 _token_amount, 48 | uint256 i, 49 | uint256 min_amount 50 | ) external; 51 | } 52 | -------------------------------------------------------------------------------- /contracts/interfaces/IPancakeRouter02.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "./IPancakeRouter01.sol"; 5 | 6 | interface IPancakeRouter02 is IPancakeRouter01 { 7 | function removeLiquidityETHSupportingFeeOnTransferTokens( 8 | address token, 9 | uint256 liquidity, 10 | uint256 amountTokenMin, 11 | uint256 amountETHMin, 12 | address to, 13 | uint256 deadline 14 | ) external returns (uint256 amountETH); 15 | 16 | function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( 17 | address token, 18 | uint256 liquidity, 19 | uint256 amountTokenMin, 20 | uint256 amountETHMin, 21 | address to, 22 | uint256 deadline, 23 | bool approveMax, 24 | uint8 v, 25 | bytes32 r, 26 | bytes32 s 27 | ) external returns (uint256 amountETH); 28 | 29 | function swapExactTokensForTokensSupportingFeeOnTransferTokens( 30 | uint256 amountIn, 31 | uint256 amountOutMin, 32 | address[] calldata path, 33 | address to, 34 | uint256 deadline 35 | ) external; 36 | 37 | function swapExactETHForTokensSupportingFeeOnTransferTokens( 38 | uint256 amountOutMin, 39 | address[] calldata path, 40 | address to, 41 | uint256 deadline 42 | ) external payable; 43 | 44 | function swapExactTokensForETHSupportingFeeOnTransferTokens( 45 | uint256 amountIn, 46 | uint256 amountOutMin, 47 | address[] calldata path, 48 | address to, 49 | uint256 deadline 50 | ) external; 51 | } 52 | -------------------------------------------------------------------------------- /contracts/interfaces/IPancakeSwapFarm.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | struct PoolInfo { 5 | uint256 accCakePerShare; 6 | uint256 lastRewardBlock; 7 | uint256 allocPoint; 8 | uint256 totalBoostedShare; 9 | bool isRegular; 10 | } 11 | 12 | interface IPancakeSwapFarm { 13 | function poolLength() external view returns (uint256); 14 | 15 | // Return reward multiplier over the given _from to _to block. 16 | function getMultiplier(uint256 _from, uint256 _to) external view returns (uint256); 17 | 18 | // View function to see pending CAKEs on frontend. 19 | function pendingCake(uint256 _pid, address _user) external view returns (uint256); 20 | 21 | // Deposit LP tokens to MasterChef for CAKE allocation. 22 | function deposit(uint256 _pid, uint256 _amount) external; 23 | 24 | // Withdraw LP tokens from MasterChef. 25 | function withdraw(uint256 _pid, uint256 _amount) external; 26 | 27 | // Stake CAKE tokens to MasterChef 28 | function enterStaking(uint256 _amount) external; 29 | 30 | // Withdraw CAKE tokens from STAKING. 31 | function leaveStaking(uint256 _amount) external; 32 | 33 | // Withdraw without caring about rewards. EMERGENCY ONLY. 34 | function emergencyWithdraw(uint256 _pid) external; 35 | 36 | function cakePerBlock(bool _isRegular) external view returns (uint256); 37 | 38 | function poolInfo(uint256 _pid) external view returns (PoolInfo memory); 39 | 40 | function totalRegularAllocPoint() external view returns (uint256); 41 | } 42 | -------------------------------------------------------------------------------- /contracts/mocks/StrategyMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | import { IStrategy } from "../interfaces/IStrategy.sol"; 8 | 9 | contract StrategyMock is IStrategy { 10 | using SafeERC20 for IERC20; 11 | 12 | uint256 public wantLockedTotal; 13 | uint256 public sharesTotal; 14 | 15 | address public wantAddress; 16 | address public farmingAddress; 17 | 18 | constructor(address _wantAddress, address _farmingAddress) { 19 | wantAddress = _wantAddress; 20 | farmingAddress = _farmingAddress; 21 | } 22 | 23 | // Transfer want tokens autoFarm -> strategy 24 | function deposit(address, uint256 _wantAmt) external returns (uint256) { 25 | IERC20(wantAddress).safeTransferFrom(address(msg.sender), address(this), _wantAmt); 26 | 27 | sharesTotal += _wantAmt; 28 | wantLockedTotal += _wantAmt; 29 | 30 | return sharesTotal; 31 | } 32 | 33 | // Transfer want tokens strategy -> autoFarm 34 | function withdraw(address, uint256 _wantAmt) external returns (uint256) { 35 | sharesTotal -= _wantAmt; 36 | wantLockedTotal -= _wantAmt; 37 | 38 | IERC20(wantAddress).safeTransfer(farmingAddress, _wantAmt); 39 | 40 | return sharesTotal; 41 | } 42 | 43 | // Main want token compounding function 44 | function earn() external pure { 45 | revert("no need for tests"); 46 | } 47 | 48 | function inCaseTokensGetStuck( 49 | address, 50 | uint256, 51 | address 52 | ) external pure { 53 | revert("no need for tests"); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /scripts/deployCurveProxyForDeposit.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { ethers, network, upgrades } from "hardhat"; 3 | import { verifyContract } from "../helpers/utils"; 4 | // eslint-disable-next-line node/no-extraneous-import 5 | import { getImplementationAddress } from "@openzeppelin/upgrades-core"; 6 | import { CurveProxyForDeposit } from "../typechain-types"; 7 | 8 | const FARMING = "0xf0fA2307392e3b0bD742437C6f80C6C56Fd8A37f"; 9 | const TOKEN0 = "0x0782b6d8c4551B9760e74c0545a9bCD90bdc41E5"; 10 | const TOKEN1 = "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56"; // BUSD 11 | const STABLE_SWAP = "0x49079D07ef47449aF808A4f36c2a8dEC975594eC"; 12 | const PID = 1; 13 | 14 | const NEW_OWNER = "0x8d388136d578dCD791D081c6042284CED6d9B0c6"; 15 | 16 | const main = async () => { 17 | const CurveProxy = await ethers.getContractFactory("CurveProxyForDeposit"); 18 | const curveProxy = (await upgrades.deployProxy(CurveProxy, [FARMING])) as CurveProxyForDeposit; 19 | await curveProxy.deployed(); 20 | const currentImplAddress = await getImplementationAddress(ethers.provider, curveProxy.address); 21 | console.log("curveProxyForDeposit address is ->", curveProxy.address); 22 | console.log("Implementation address ->", currentImplAddress); 23 | const addresses = { 24 | curveProxyForDeposit: curveProxy.address, 25 | curveProxyForDepositImpl: currentImplAddress, 26 | }; 27 | const jsonAddresses = JSON.stringify(addresses); 28 | fs.writeFileSync(`../${network.name}CurveProxyForDeposit.json`, jsonAddresses); 29 | console.log("Addresses saved!"); 30 | 31 | await curveProxy.addSupportedTokens(STABLE_SWAP, TOKEN0, TOKEN1, PID); 32 | await curveProxy.transferOwnership(NEW_OWNER); 33 | 34 | await verifyContract(currentImplAddress, []); 35 | }; 36 | 37 | main(); 38 | -------------------------------------------------------------------------------- /contracts/mocks/PancakeRouterMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import { IERC20MintBurnable } from "./interfaces/IERC20MintBurnable.sol"; 6 | 7 | contract PancakeRouterMock { 8 | IERC20 public earn; 9 | IERC20 public hay; 10 | IERC20 public busd; 11 | IERC20MintBurnable public lp; 12 | 13 | uint256 public constant HAY_BUSD = 1e18; 14 | uint256 public constant EARN_STABLE = 1e18 / 5; 15 | uint256 public constant STABLE_EARN = 5 * 1e18; 16 | 17 | mapping(IERC20 => mapping(IERC20 => uint256)) public tokenCoefficients; 18 | 19 | constructor( 20 | IERC20 _earn, 21 | IERC20 _hay, 22 | IERC20 _busd, 23 | IERC20MintBurnable _lp 24 | ) { 25 | earn = _earn; 26 | hay = _hay; 27 | busd = _busd; 28 | lp = _lp; 29 | tokenCoefficients[hay][busd] = HAY_BUSD; 30 | tokenCoefficients[busd][hay] = HAY_BUSD; 31 | tokenCoefficients[earn][hay] = EARN_STABLE; 32 | tokenCoefficients[earn][busd] = EARN_STABLE; 33 | tokenCoefficients[hay][earn] = STABLE_EARN; 34 | tokenCoefficients[busd][earn] = STABLE_EARN; 35 | } 36 | 37 | function addliquidity( 38 | IERC20 tokenA, 39 | IERC20 tokenB, 40 | uint256 amountADesired, 41 | uint256 amountBDesired, 42 | uint256, 43 | uint256, 44 | address to, 45 | uint256 deadline 46 | ) 47 | external 48 | returns ( 49 | uint256 amountA, 50 | uint256 amountB, 51 | uint256 liquidity 52 | ) 53 | { 54 | require(tokenA == earn || tokenA == hay || tokenA == busd, "Wrong Token"); 55 | require((tokenB == earn || tokenB == hay || tokenB == busd) && tokenA != tokenB, "Wrong Token"); 56 | require(deadline >= block.timestamp, "deadline passed"); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "helio-farming", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "format": "prettier --write", 7 | "format-all": "prettier --write .", 8 | "prettier-check": "prettier --check .", 9 | "coverage": "hardhat coverage", 10 | "eslint-check": "eslint '**/*.{js,ts}'", 11 | "solhint-check": "solhint 'contracts/**/*.sol'", 12 | "size-contracts": "hardhat compile && hardhat size-contracts", 13 | "test": "hardhat compile && hardhat test", 14 | "compile": "hardhat compile", 15 | "clean": "hardhat clean", 16 | "runScript": "hardhat run", 17 | "verify": "hardhat verify" 18 | }, 19 | "devDependencies": { 20 | "@chainlink/contracts": "^0.4.2", 21 | "@nomiclabs/hardhat-ethers": "^2.1.1", 22 | "@nomiclabs/hardhat-etherscan": "^3.1.0", 23 | "@nomiclabs/hardhat-waffle": "^2.0.3", 24 | "@openzeppelin/contracts": "^4.7.3", 25 | "@openzeppelin/contracts-upgradeable": "^4.7.3", 26 | "@openzeppelin/hardhat-upgrades": "^1.20.0", 27 | "@typechain/ethers-v5": "^10.1.0", 28 | "@typechain/hardhat": "^6.1.2", 29 | "@types/chai": "^4.3.3", 30 | "@types/chai-as-promised": "^7.1.5", 31 | "@types/mocha": "^9.1.1", 32 | "@types/node": "^18.7.6", 33 | "@typescript-eslint/eslint-plugin": "^5.33.1", 34 | "@typescript-eslint/parser": "^5.33.1", 35 | "chai": "^4.3.6", 36 | "chai-as-promised": "^7.1.1", 37 | "dotenv": "^16.0.1", 38 | "eslint": "^8.22.0", 39 | "eslint-config-prettier": "^8.5.0", 40 | "eslint-config-standard": "^17.0.0", 41 | "eslint-import-resolver-typescript": "^3.4.2", 42 | "eslint-plugin-import": "^2.26.0", 43 | "eslint-plugin-n": "^15.2.4", 44 | "eslint-plugin-node": "^11.1.0", 45 | "eslint-plugin-prettier": "^4.2.1", 46 | "eslint-plugin-promise": "^6.0.0", 47 | "ethereum-waffle": "^3.4.4", 48 | "ethers": "^5.6.9", 49 | "hardhat": "^2.10.2", 50 | "hardhat-contract-sizer": "^2.6.1", 51 | "hardhat-gas-reporter": "^1.0.8", 52 | "prettier": "^2.7.1", 53 | "prettier-plugin-solidity": "^1.0.0-beta.24", 54 | "solhint": "^3.3.7", 55 | "solidity-coverage": "^0.7.21", 56 | "ts-node": "^10.9.1", 57 | "typechain": "^8.1.0", 58 | "typescript": "^4.7.4" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /scripts/deployStableCoinStrategy.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { ethers, network, upgrades } from "hardhat"; 3 | import { verifyContract } from "../helpers/utils"; 4 | // eslint-disable-next-line node/no-extraneous-import 5 | import { getImplementationAddress } from "@openzeppelin/upgrades-core"; 6 | 7 | const PID = 121; 8 | const MIN_EARN_AMT = "10000000000"; 9 | const FARMING = "0xf0fA2307392e3b0bD742437C6f80C6C56Fd8A37f"; 10 | const MASTERCHEF = "0xa5f8C5Dbd5F286960b9d90548680aE5ebFf07652"; 11 | const WANT = "0xB6040A9F294477dDAdf5543a24E5463B8F2423Ae"; 12 | const CAKE = "0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82"; 13 | const TOKEN0 = "0x0782b6d8c4551B9760e74c0545a9bCD90bdc41E5"; 14 | const TOKEN1 = "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56"; // BUSD 15 | const I0 = 0; 16 | const I1 = 1; 17 | const ROUTER = "0x10ED43C718714eb63d5aA57B78B54704E256024E"; 18 | const STABLE_SWAP = "0x49079D07ef47449aF808A4f36c2a8dEC975594eC"; 19 | const EARNED_TO_TOKEN1_PATH = [CAKE, TOKEN1]; 20 | 21 | const main = async () => { 22 | const StableStrategy = await ethers.getContractFactory("StableCoinStrategyCurve"); 23 | 24 | const stableStrategy = await upgrades.deployProxy(StableStrategy, [ 25 | PID, 26 | I0, 27 | I1, 28 | MIN_EARN_AMT, 29 | false, 30 | [MASTERCHEF, WANT, CAKE, TOKEN0, TOKEN1, ROUTER, STABLE_SWAP, FARMING], 31 | EARNED_TO_TOKEN1_PATH, 32 | ]); 33 | await stableStrategy.deployed(); 34 | 35 | const currentImplAddress = await getImplementationAddress( 36 | ethers.provider, 37 | stableStrategy.address 38 | ); 39 | const stableStrategyImplementation = currentImplAddress; 40 | console.log("Strategy address is ->", stableStrategy.address); 41 | console.log("Implementation address ->", currentImplAddress); 42 | 43 | const addresses = { 44 | stableStrategy: stableStrategy.address, 45 | stableStrategyImplementation, 46 | }; 47 | const jsonAddresses = JSON.stringify(addresses); 48 | fs.writeFileSync(`../${network.name}StableCoinStrategyAddresses.json`, jsonAddresses); 49 | console.log("Addresses saved!"); 50 | 51 | await verifyContract(stableStrategyImplementation, []); 52 | }; 53 | 54 | main() 55 | .then(() => { 56 | console.log("Success"); 57 | }) 58 | .catch((err) => { 59 | console.log(err); 60 | }); 61 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | 3 | import { HardhatUserConfig, task } from "hardhat/config"; 4 | import "@nomiclabs/hardhat-etherscan"; 5 | import "@nomiclabs/hardhat-waffle"; 6 | import "@typechain/hardhat"; 7 | import "@openzeppelin/hardhat-upgrades"; 8 | import "hardhat-gas-reporter"; 9 | import "hardhat-contract-sizer"; 10 | import "solidity-coverage"; 11 | 12 | // This is a sample Hardhat task. To learn how to create your own go to 13 | // https://hardhat.org/guides/create-task.html 14 | task("accounts", "Prints the list of accounts", async (_, hre) => { 15 | const accounts = await hre.ethers.getSigners(); 16 | 17 | for (const account of accounts) { 18 | console.log(account.address); 19 | } 20 | }); 21 | 22 | const apiKey: Record = {}; 23 | if (process.env.BSC_API_KEY) { 24 | apiKey.bscTestnet = process.env.BSC_API_KEY; 25 | apiKey.bsc = process.env.BSC_API_KEY; 26 | } 27 | 28 | const pks = []; 29 | if (process.env.DEPLOYER_PK) { 30 | pks.push(process.env.DEPLOYER_PK); 31 | } 32 | if (process.env.PK_1) { 33 | pks.push(process.env.PK_1); 34 | } 35 | if (process.env.PK_2) { 36 | pks.push(process.env.PK_2); 37 | } 38 | 39 | let forking; 40 | if (process.env.FORKING_URL) { 41 | forking = { 42 | url: process.env.FORKING_URL, 43 | }; 44 | } 45 | 46 | // You need to export an object to set up your config 47 | // Go to https://hardhat.org/config/ to learn more 48 | const config: HardhatUserConfig = { 49 | solidity: { 50 | version: "0.8.15", 51 | settings: { 52 | optimizer: { 53 | enabled: true, 54 | runs: 201, 55 | }, 56 | }, 57 | }, 58 | networks: { 59 | hardhat: { 60 | forking, 61 | accounts: { 62 | mnemonic: "test test test test test test test test test test test test", 63 | count: 10, 64 | accountsBalance: "100000000000000000000000000", 65 | }, 66 | }, 67 | bscTestnet: { 68 | url: process.env.BSC_TESTNET_URL || "", 69 | accounts: pks, 70 | }, 71 | bsc: { 72 | url: process.env.BSC_URL || "", 73 | accounts: pks, 74 | }, 75 | }, 76 | gasReporter: { 77 | enabled: process.env.REPORT_GAS !== undefined, 78 | currency: "USD", 79 | }, 80 | etherscan: { 81 | apiKey, 82 | }, 83 | }; 84 | 85 | export default config; 86 | -------------------------------------------------------------------------------- /helpers/utils.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "ethers"; 2 | import hre, { ethers, network } from "hardhat"; 3 | 4 | export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); 5 | 6 | export const verifyContract = async ( 7 | contractAddress: string, 8 | constructorArguments: Array 9 | ) => { 10 | try { 11 | const tx = await hre.run("verify:verify", { 12 | address: contractAddress, 13 | constructorArguments, 14 | }); 15 | console.log(tx); 16 | 17 | await sleep(16000); 18 | } catch (error) { 19 | console.log("error is ->"); 20 | console.log(error); 21 | console.log("cannot verify contract", contractAddress); 22 | await sleep(16000); 23 | } 24 | console.log("contract", contractAddress, "verified successfully"); 25 | }; 26 | 27 | export const advanceTime = async (seconds: number) => { 28 | await network.provider.send("evm_increaseTime", [seconds]); 29 | await network.provider.send("evm_mine"); 30 | }; 31 | 32 | export const advanceBlock = async (blockCount: number) => { 33 | for (let i = 0; i < blockCount; i++) { 34 | await network.provider.send("evm_mine"); 35 | } 36 | }; 37 | 38 | export const advanceBlockAndTime = async (blockCount: number, seconds: number) => { 39 | const secondPerBlock = Math.floor(seconds / blockCount); 40 | for (let i = 0; i < blockCount; i++) { 41 | await advanceTime(secondPerBlock); 42 | } 43 | }; 44 | 45 | export const setTimestamp = async (seconds: number) => { 46 | await network.provider.send("evm_setNextBlockTimestamp", [seconds]); 47 | await network.provider.send("evm_mine"); 48 | }; 49 | 50 | export const getTimestamp = async (): Promise => { 51 | const blockNumber = await ethers.provider.getBlockNumber(); 52 | const block = await ethers.provider.getBlock(blockNumber); 53 | return block.timestamp; 54 | }; 55 | 56 | export const daysToSeconds = (days: BigNumber): BigNumber => { 57 | return hoursToSeconds(days.mul(24)); 58 | }; 59 | 60 | export const hoursToSeconds = (hours: BigNumber): BigNumber => { 61 | return minutesToSeconds(hours.mul(60)); 62 | }; 63 | 64 | export const minutesToSeconds = (minutes: BigNumber): BigNumber => { 65 | return minutes.mul(60); 66 | }; 67 | 68 | export const getNextTimestampDivisibleBy = async (num: number): Promise => { 69 | const blockTimestamp = await getTimestamp(); 70 | const numCount = BigNumber.from(blockTimestamp).div(num); 71 | return numCount.add(1).mul(num); 72 | }; 73 | 74 | export default { 75 | sleep, 76 | verifyContract, 77 | advanceTime, 78 | advanceBlock, 79 | advanceBlockAndTime, 80 | setTimestamp, 81 | getTimestamp, 82 | daysToSeconds, 83 | hoursToSeconds, 84 | minutesToSeconds, 85 | getNextTimestampDivisibleBy, 86 | }; 87 | -------------------------------------------------------------------------------- /scripts/apr.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "ethers"; 2 | import { ethers } from "hardhat"; 3 | 4 | const ten = BigNumber.from(10); 5 | const tenPow18 = ten.pow(18); 6 | 7 | const blocksPerDay = BigNumber.from(28682); 8 | const dayOfYear = BigNumber.from(365); 9 | const hundred = BigNumber.from(100); 10 | const secondsInYear = BigNumber.from(31536000); 11 | 12 | const cakePrice = BigNumber.from(486); // you need to get it from some api 13 | 14 | const main = async () => { 15 | const lp = await ethers.getContractAt( 16 | "IPancakePair", 17 | "0x70c26e9805ec5Df3d4aB0b2a3dF86BBA2231B6c1" 18 | ); 19 | const masterChef = await ethers.getContractAt( 20 | "IPancakeSwapFarm", 21 | "0xa5f8C5Dbd5F286960b9d90548680aE5ebFf07652" 22 | ); 23 | const farming = await ethers.getContractAt( 24 | "Farming", 25 | "0xf0fA2307392e3b0bD742437C6f80C6C56Fd8A37f" 26 | ); 27 | const incentiveVoting = await ethers.getContractAt( 28 | "IncentiveVoting", 29 | "0xdE1F4c0DD8C22b421851Fb51862F265D7564bEf7" 30 | ); 31 | const pancakeStrategy = await ethers.getContractAt( 32 | "PancakeStrategyV2", 33 | "0x5A2CcC1f8BB9a3048885E5F38bB48463E6314B7C" 34 | ); 35 | // about lp 36 | const totalSupply = await lp.totalSupply(); 37 | const [reserve0, reserve1] = await lp.getReserves(); 38 | // const totalSupplyNum = Number(totalSupply.toString()) / 10 ** 18; 39 | // const reserve0Num = Number(reserve0.toString()) / 10 ** 18; 40 | // const reserve1Num = Number(reserve1.toString()) / 10 ** 18; 41 | const totalLPPrice = reserve0.add(reserve1); 42 | const lpPrice = totalLPPrice.mul(tenPow18).div(totalSupply).toString(); 43 | 44 | const cakePerBlock = await masterChef.cakePerBlock(true); 45 | const poolInfo = await masterChef.poolInfo(115); 46 | const tvl = poolInfo.totalBoostedShare; 47 | const allocPoint = poolInfo.allocPoint; 48 | const totalAllocPoint = await masterChef.totalRegularAllocPoint(); 49 | 50 | const tvlLpPrice = tvl.mul(lpPrice).div(tenPow18); 51 | const totalCakeRewardPrice = allocPoint 52 | .mul(cakePerBlock) 53 | .mul(blocksPerDay) 54 | .mul(dayOfYear) 55 | .mul(cakePrice) 56 | .div(totalAllocPoint) 57 | .div(100); 58 | 59 | const cakeApr = totalCakeRewardPrice.mul(tenPow18).mul(hundred).div(tvlLpPrice); 60 | 61 | const currentWeek = await incentiveVoting.getWeek(); 62 | const rewardsPerSecond = await incentiveVoting.getRewardsPerSecond(0, currentWeek); 63 | const tvlOur = await pancakeStrategy.wantLockedTotal(); 64 | const tvlOurLpPrice = tvlOur.mul(lpPrice).div(tenPow18); 65 | const totalHayRewards = rewardsPerSecond.mul(secondsInYear); 66 | 67 | const ourApr = tvlOur.isZero() 68 | ? BigNumber.from(0) 69 | : totalHayRewards.mul(tenPow18).mul(hundred).div(tvlOurLpPrice); 70 | 71 | console.log("cake apr is ->", cakeApr.toString()); 72 | console.log("our apr is ->", ourApr.toString()); 73 | }; 74 | 75 | main(); 76 | -------------------------------------------------------------------------------- /scripts/apr1.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "ethers"; 2 | import { ethers } from "hardhat"; 3 | 4 | const ten = BigNumber.from(10); 5 | const tenPow18 = ten.pow(18); 6 | 7 | const blocksPerDay = BigNumber.from(28682); 8 | const dayOfYear = BigNumber.from(365); 9 | const hundred = BigNumber.from(100); 10 | const secondsInYear = BigNumber.from(31536000); 11 | 12 | const cakePrice = BigNumber.from(486); // you need to get it from some api 13 | 14 | const main = async () => { 15 | const lp = await ethers.getContractAt("IERC20", "0xB6040A9F294477dDAdf5543a24E5463B8F2423Ae"); 16 | const masterChef = await ethers.getContractAt( 17 | "IPancakeSwapFarm", 18 | "0xa5f8C5Dbd5F286960b9d90548680aE5ebFf07652" 19 | ); 20 | const incentiveVoting = await ethers.getContractAt( 21 | "IncentiveVoting", 22 | "0xdE1F4c0DD8C22b421851Fb51862F265D7564bEf7" 23 | ); 24 | const stableStrategy = await ethers.getContractAt( 25 | "StableCoinStrategyCurve", 26 | "0x5A2CcC1f8BB9a3048885E5F38bB48463E6314B7C" 27 | ); 28 | const stableSwap = await ethers.getContractAt( 29 | "IStable", 30 | "0x49079D07ef47449aF808A4f36c2a8dEC975594eC" 31 | ); 32 | // about lp 33 | const totalSupply = await lp.totalSupply(); 34 | // const totalSupplyNum = Number(totalSupply.toString()) / 10 ** 18; 35 | // const reserve0Num = Number(reserve0.toString()) / 10 ** 18; 36 | // const reserve1Num = Number(reserve1.toString()) / 10 ** 18; 37 | const reserve0 = await stableSwap.balances(0); 38 | const reserve1 = await stableSwap.balances(1); 39 | const totalLPPrice = reserve0.add(reserve1); 40 | const lpPrice = totalLPPrice.mul(tenPow18).div(totalSupply).toString(); 41 | 42 | const cakePerBlock = await masterChef.cakePerBlock(true); 43 | const poolInfo = await masterChef.poolInfo(121); 44 | const tvl = poolInfo.totalBoostedShare; 45 | const allocPoint = poolInfo.allocPoint; 46 | const totalAllocPoint = await masterChef.totalRegularAllocPoint(); 47 | 48 | const tvlLpPrice = tvl.mul(lpPrice).div(tenPow18); 49 | const totalCakeRewardPrice = allocPoint 50 | .mul(cakePerBlock) 51 | .mul(blocksPerDay) 52 | .mul(dayOfYear) 53 | .mul(cakePrice) 54 | .div(totalAllocPoint) 55 | .div(100); 56 | 57 | const cakeApr = totalCakeRewardPrice.mul(tenPow18).mul(hundred).div(tvlLpPrice); 58 | 59 | const currentWeek = await incentiveVoting.getWeek(); 60 | const rewardsPerSecond = await incentiveVoting.getRewardsPerSecond(1, currentWeek); 61 | const tvlOur = await stableStrategy.wantLockedTotal(); 62 | const tvlOurLpPrice = tvlOur.mul(lpPrice).div(tenPow18); 63 | const totalHayRewards = rewardsPerSecond.mul(secondsInYear); 64 | 65 | const ourApr = tvlOur.isZero() 66 | ? BigNumber.from(0) 67 | : totalHayRewards.mul(tenPow18).mul(hundred).div(tvlOurLpPrice); 68 | 69 | console.log("cake apr is ->", cakeApr.toString()); 70 | console.log("our apr is ->", ourApr.toString()); 71 | }; 72 | 73 | main(); 74 | -------------------------------------------------------------------------------- /contracts/interfaces/IPancakePair.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | interface IPancakePair { 5 | event Approval(address indexed owner, address indexed spender, uint256 value); 6 | event Transfer(address indexed from, address indexed to, uint256 value); 7 | 8 | function name() external pure returns (string memory); 9 | 10 | function symbol() external pure returns (string memory); 11 | 12 | function decimals() external pure returns (uint8); 13 | 14 | function totalSupply() external view returns (uint256); 15 | 16 | function balanceOf(address owner) external view returns (uint256); 17 | 18 | function allowance(address owner, address spender) external view returns (uint256); 19 | 20 | function approve(address spender, uint256 value) external returns (bool); 21 | 22 | function transfer(address to, uint256 value) external returns (bool); 23 | 24 | function transferFrom( 25 | address from, 26 | address to, 27 | uint256 value 28 | ) external returns (bool); 29 | 30 | function DOMAIN_SEPARATOR() external view returns (bytes32); 31 | 32 | function PERMIT_TYPEHASH() external pure returns (bytes32); 33 | 34 | function nonces(address owner) external view returns (uint256); 35 | 36 | function permit( 37 | address owner, 38 | address spender, 39 | uint256 value, 40 | uint256 deadline, 41 | uint8 v, 42 | bytes32 r, 43 | bytes32 s 44 | ) external; 45 | 46 | event Mint(address indexed sender, uint256 amount0, uint256 amount1); 47 | event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); 48 | event Swap( 49 | address indexed sender, 50 | uint256 amount0In, 51 | uint256 amount1In, 52 | uint256 amount0Out, 53 | uint256 amount1Out, 54 | address indexed to 55 | ); 56 | event Sync(uint112 reserve0, uint112 reserve1); 57 | 58 | function MINIMUM_LIQUIDITY() external pure returns (uint256); 59 | 60 | function factory() external view returns (address); 61 | 62 | function token0() external view returns (address); 63 | 64 | function token1() external view returns (address); 65 | 66 | function getReserves() 67 | external 68 | view 69 | returns ( 70 | uint112 reserve0, 71 | uint112 reserve1, 72 | uint32 blockTimestampLast 73 | ); 74 | 75 | function price0CumulativeLast() external view returns (uint256); 76 | 77 | function price1CumulativeLast() external view returns (uint256); 78 | 79 | function kLast() external view returns (uint256); 80 | 81 | function mint(address to) external returns (uint256 liquidity); 82 | 83 | function burn(address to) external returns (uint256 amount0, uint256 amount1); 84 | 85 | function swap( 86 | uint256 amount0Out, 87 | uint256 amount1Out, 88 | address to, 89 | bytes calldata data 90 | ) external; 91 | 92 | function skim(address to) external; 93 | 94 | function sync() external; 95 | 96 | function initialize(address, address) external; 97 | } 98 | -------------------------------------------------------------------------------- /contracts/upgrades/IncentiveVotingV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { ITokenBonding } from "../interfaces/ITokenBonding.sol"; 5 | 6 | import { IncentiveVoting } from "../IncentiveVoting.sol"; 7 | 8 | contract IncentiveVotingV2 is IncentiveVoting { 9 | /** 10 | @notice Get the amount of unused votes for for the current week being voted on 11 | @param _user Address to query 12 | @return uint Amount of unused votes 13 | */ 14 | function availableVotes(address _user) external view virtual override returns (uint256) { 15 | uint256 week = getWeek(); 16 | uint256 usedVotes = userVotes[_user][week]; 17 | uint256 _totalVotes = tokenBonding.userWeight(_user) / 1e18; 18 | return _totalVotes - usedVotes; 19 | } 20 | 21 | /** 22 | @notice Allocate votes toward LP tokens to receive emissions in the following week 23 | @dev A user may vote as many times as they like within a week, so long as their total 24 | available votes are not exceeded. If they receive additional votes by locking more 25 | tokens within `tokenBonding`, they can vote immediately. 26 | 27 | Votes can only be added - not modified or removed. Votes only apply to the 28 | following week - they do not carry over. A user must resubmit their vote each 29 | week. 30 | @param _pids List of pool ids of LP tokens to vote for 31 | @param _votes Votes to allocate to `_tokens`. Values are additive, they do 32 | not include previous votes. For example, if you have already 33 | allocated 100 votes and wish to allocate a total of 300, 34 | the vote amount should be given as 200. 35 | */ 36 | function vote(uint256[] calldata _pids, uint256[] calldata _votes) external virtual override { 37 | require(_pids.length == _votes.length, "Input length mismatch"); 38 | 39 | // update rewards per second, if required 40 | uint256 week = getWeek(); 41 | 42 | // update accounting for this week's votes 43 | uint256 usedVotes = userVotes[msg.sender][week]; 44 | for (uint256 i = 0; i < _pids.length; i++) { 45 | uint256 pid = _pids[i]; 46 | uint256 amount = _votes[i]; 47 | require(tokenByPid[pid] != address(0), "Not approved for incentives"); 48 | pidVotes[pid][week] += amount; 49 | totalVotes[week] += amount; 50 | userPidVotes[msg.sender][pid][week] += amount; 51 | usedVotes += amount; 52 | } 53 | 54 | // make sure user has not exceeded available votes 55 | uint256 _totalVotes = tokenBonding.userWeight(msg.sender) / 1e18; 56 | require(usedVotes <= _totalVotes, "Available votes exceeded"); 57 | userVotes[msg.sender][week] = usedVotes; 58 | 59 | emit VotedForIncentives(msg.sender, _pids, _votes, usedVotes, _totalVotes); 60 | } 61 | 62 | function setTokenBonding(address _tokenBonding) external { 63 | require(address(tokenBonding) == address(0), "already setted!"); 64 | tokenBonding = ITokenBonding(_tokenBonding); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /contracts/ProxyUpgrades/ProxyAdmin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (proxy/transparent/ProxyAdmin.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "./TransparentUpgradeableProxy.sol"; 7 | import "@openzeppelin/contracts/access/Ownable.sol"; 8 | 9 | /** 10 | * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an 11 | * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}. 12 | */ 13 | contract ProxyAdmin is Ownable { 14 | /** 15 | * @dev Returns the current implementation of `proxy`. 16 | * 17 | * Requirements: 18 | * 19 | * - This contract must be the admin of `proxy`. 20 | */ 21 | function getProxyImplementation(TransparentUpgradeableProxy proxy) 22 | public 23 | view 24 | virtual 25 | returns (address) 26 | { 27 | // We need to manually run the static call since the getter cannot be flagged as view 28 | // bytes4(keccak256("implementation()")) == 0x5c60da1b 29 | (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b"); 30 | require(success); 31 | return abi.decode(returndata, (address)); 32 | } 33 | 34 | /** 35 | * @dev Returns the current admin of `proxy`. 36 | * 37 | * Requirements: 38 | * 39 | * - This contract must be the admin of `proxy`. 40 | */ 41 | function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) { 42 | // We need to manually run the static call since the getter cannot be flagged as view 43 | // bytes4(keccak256("admin()")) == 0xf851a440 44 | (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440"); 45 | require(success); 46 | return abi.decode(returndata, (address)); 47 | } 48 | 49 | /** 50 | * @dev Changes the admin of `proxy` to `newAdmin`. 51 | * 52 | * Requirements: 53 | * 54 | * - This contract must be the current admin of `proxy`. 55 | */ 56 | function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) 57 | public 58 | virtual 59 | onlyOwner 60 | { 61 | proxy.changeAdmin(newAdmin); 62 | } 63 | 64 | /** 65 | * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}. 66 | * 67 | * Requirements: 68 | * 69 | * - This contract must be the admin of `proxy`. 70 | */ 71 | function upgrade(TransparentUpgradeableProxy proxy, address implementation) 72 | public 73 | virtual 74 | onlyOwner 75 | { 76 | proxy.upgradeTo(implementation); 77 | } 78 | 79 | /** 80 | * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See 81 | * {TransparentUpgradeableProxy-upgradeToAndCall}. 82 | * 83 | * Requirements: 84 | * 85 | * - This contract must be the admin of `proxy`. 86 | */ 87 | function upgradeAndCall( 88 | TransparentUpgradeableProxy proxy, 89 | address implementation, 90 | bytes memory data 91 | ) public payable virtual onlyOwner { 92 | proxy.upgradeToAndCall{ value: msg.value }(implementation, data); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /contracts/CurveProxyForDeposit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 5 | import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 6 | import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 7 | import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; 8 | 9 | import { IFarming } from "./interfaces/IFarming.sol"; 10 | import { IPancakeFactory } from "./interfaces/IPancakeFactory.sol"; 11 | import { IPancakeRouter02 } from "./interfaces/IPancakeRouter02.sol"; 12 | 13 | uint256 constant N_COINS = 2; 14 | 15 | // solhint-disable func-name-mixedcase 16 | // solhint-disable var-name-mixedcase 17 | interface IStableSwap { 18 | function add_liquidity(uint256[N_COINS] memory amounts, uint256 min_mint_amount) external; 19 | 20 | function token() external returns (IERC20Upgradeable); 21 | } 22 | 23 | struct SwapInfo { 24 | IERC20Upgradeable token0; 25 | IERC20Upgradeable token1; 26 | IERC20Upgradeable lp; 27 | uint256 pid; 28 | } 29 | 30 | contract CurveProxyForDeposit is Initializable, OwnableUpgradeable { 31 | using SafeERC20Upgradeable for IERC20Upgradeable; 32 | 33 | event StableSwapInfoChanged(address indexed stableSwap, SwapInfo swapInfo); 34 | 35 | IFarming public farming; 36 | 37 | mapping(IStableSwap => SwapInfo) public supportedPids; 38 | 39 | function initialize(IFarming _farming) public initializer { 40 | __Ownable_init(); 41 | farming = _farming; 42 | } 43 | 44 | function depositToFarming( 45 | IStableSwap stableSwap, 46 | uint256 amount0, 47 | uint256 amount1, 48 | uint256 minMintAmount 49 | ) external { 50 | SwapInfo memory swapInfo = supportedPids[stableSwap]; 51 | swapInfo.token0.safeTransferFrom(msg.sender, address(this), amount0); 52 | swapInfo.token1.safeTransferFrom(msg.sender, address(this), amount1); 53 | uint256[N_COINS] memory amounts = [amount0, amount1]; 54 | stableSwap.add_liquidity(amounts, minMintAmount); 55 | farming.deposit(swapInfo.pid, swapInfo.lp.balanceOf(address(this)), false, msg.sender); 56 | uint256 tokenABal = swapInfo.token0.balanceOf(address(this)); 57 | uint256 tokenBBal = swapInfo.token1.balanceOf(address(this)); 58 | if (tokenABal > 0) { 59 | swapInfo.token0.safeTransfer(msg.sender, tokenABal); 60 | } 61 | if (tokenBBal > 0) { 62 | swapInfo.token1.safeTransfer(msg.sender, tokenBBal); 63 | } 64 | } 65 | 66 | function addSupportedTokens( 67 | IStableSwap stableSwap, 68 | IERC20Upgradeable token0, 69 | IERC20Upgradeable token1, 70 | uint256 pid 71 | ) external onlyOwner { 72 | IERC20Upgradeable lp = stableSwap.token(); 73 | IERC20Upgradeable(token0).approve(address(stableSwap), type(uint256).max); 74 | IERC20Upgradeable(token1).approve(address(stableSwap), type(uint256).max); 75 | lp.approve(address(farming), type(uint256).max); 76 | supportedPids[stableSwap] = SwapInfo({ token0: token0, token1: token1, lp: lp, pid: pid }); 77 | emit StableSwapInfoChanged(address(stableSwap), supportedPids[stableSwap]); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /contracts/PancakeProxyForDeposit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 5 | import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 6 | import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 7 | import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; 8 | 9 | import { IFarming } from "./interfaces/IFarming.sol"; 10 | import { IPancakeFactory } from "./interfaces/IPancakeFactory.sol"; 11 | import { IPancakeRouter02 } from "./interfaces/IPancakeRouter02.sol"; 12 | 13 | struct PairInfo { 14 | IERC20Upgradeable pair; 15 | uint256 pid; 16 | } 17 | 18 | contract PancakeProxyForDeposit is Initializable, OwnableUpgradeable { 19 | using SafeERC20Upgradeable for IERC20Upgradeable; 20 | 21 | event PidChanged(address indexed token0, address indexed token1, uint256 indexed pid); 22 | 23 | IFarming public farming; 24 | IPancakeRouter02 public router; 25 | IPancakeFactory public factory; 26 | 27 | mapping(address => mapping(address => PairInfo)) public supportedPids; 28 | 29 | function initialize(IFarming _farming, IPancakeRouter02 _router) public initializer { 30 | __Ownable_init(); 31 | farming = _farming; 32 | router = _router; 33 | factory = IPancakeFactory(router.factory()); 34 | } 35 | 36 | function depositToFarming( 37 | IERC20Upgradeable tokenA, 38 | IERC20Upgradeable tokenB, 39 | uint256 amountA, 40 | uint256 amountB, 41 | uint256 amountAMin, 42 | uint256 amountBMin 43 | ) external { 44 | tokenA.safeTransferFrom(msg.sender, address(this), amountA); 45 | tokenB.safeTransferFrom(msg.sender, address(this), amountB); 46 | router.addLiquidity( 47 | address(tokenA), 48 | address(tokenB), 49 | amountA, 50 | amountB, 51 | amountAMin, 52 | amountBMin, 53 | address(this), 54 | block.timestamp 55 | ); 56 | PairInfo memory pairInfo = getPairInfo(address(tokenA), address(tokenB)); 57 | require(address(pairInfo.pair) != address(0), "tokens are not supported"); 58 | farming.deposit(pairInfo.pid, pairInfo.pair.balanceOf(address(this)), false, msg.sender); 59 | uint256 tokenABal = tokenA.balanceOf(address(this)); 60 | uint256 tokenBBal = tokenB.balanceOf(address(this)); 61 | if (tokenABal > 0) { 62 | tokenA.safeTransfer(msg.sender, tokenABal); 63 | } 64 | if (tokenBBal > 0) { 65 | tokenB.safeTransfer(msg.sender, tokenBBal); 66 | } 67 | } 68 | 69 | function addSupportedTokens( 70 | address tokenA, 71 | address tokenB, 72 | uint256 pid 73 | ) external onlyOwner { 74 | (address token0, address token1) = sortTokens(tokenA, tokenB); 75 | IERC20Upgradeable pair = IERC20Upgradeable(factory.getPair(token0, token1)); 76 | IERC20Upgradeable(token0).approve(address(router), type(uint256).max); 77 | IERC20Upgradeable(token1).approve(address(router), type(uint256).max); 78 | pair.approve(address(farming), type(uint256).max); 79 | supportedPids[token0][token1] = PairInfo({ pair: pair, pid: pid }); 80 | emit PidChanged(token0, token1, pid); 81 | } 82 | 83 | function sortTokens(address tokenA, address tokenB) 84 | internal 85 | pure 86 | returns (address token0, address token1) 87 | { 88 | (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); 89 | require(token0 != address(0), "ZERO_ADDRESS"); 90 | } 91 | 92 | function getPairInfo(address tokenA, address tokenB) internal view returns (PairInfo memory) { 93 | (address token0, address token1) = sortTokens(tokenA, tokenB); 94 | return supportedPids[token0][token1]; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /scripts/deployAllTestnet.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "ethers"; 2 | import fs from "fs"; 3 | import { ethers, network, upgrades } from "hardhat"; 4 | import { verifyContract } from "../helpers/utils"; 5 | // eslint-disable-next-line node/no-extraneous-import 6 | import { getImplementationAddress } from "@openzeppelin/upgrades-core"; 7 | 8 | const { AddressZero } = ethers.constants; 9 | 10 | const START_TIME = BigNumber.from(Math.floor(Date.now() / 1000) + 100); 11 | const HAY = "0x7adC9A28Fab850586dB99E7234EA2Eb7014950fA"; 12 | const MIN_EARN_AMT = "10000000000"; 13 | const MASTERCHEF = AddressZero; 14 | const WANT = "0xE041AB7Ea825C06d4BF7eA3182F3d4EC4De7E83E"; 15 | const CAKE = AddressZero; 16 | const TOKEN0 = "0x78867BbEeF44f2326bF8DDd1941a4439382EF2A7"; // BUSD 17 | const TOKEN1 = "0x7adC9A28Fab850586dB99E7234EA2Eb7014950fA"; 18 | const ROUTER = "0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3"; 19 | const EARNED_TO_TOKEN0_PATH: string[] = []; 20 | const EARNED_TO_TOKEN1_PATH: string[] = []; 21 | 22 | const main = async () => { 23 | const FarmingFactory = await ethers.getContractFactory("Farming"); 24 | const IncentiveVotingFactory = await ethers.getContractFactory("IncentiveVoting"); 25 | const PancakeStrategyFactory = await ethers.getContractFactory("PancakeStrategy"); 26 | const PancakeProxyForDepositFactory = await ethers.getContractFactory("PancakeProxyForDeposit"); 27 | 28 | console.log(START_TIME.toString()); 29 | console.log("Start"); 30 | const incentiveVoting = await upgrades.deployProxy(IncentiveVotingFactory, [START_TIME]); 31 | await incentiveVoting.deployed(); 32 | let currentImplAddress = await getImplementationAddress(ethers.provider, incentiveVoting.address); 33 | const incentiveVotingImpl = currentImplAddress; 34 | 35 | const farming = await upgrades.deployProxy(FarmingFactory, [HAY, incentiveVoting.address]); 36 | await farming.deployed(); 37 | currentImplAddress = await getImplementationAddress(ethers.provider, farming.address); 38 | const farmingImpl = currentImplAddress; 39 | 40 | const pancakeStrategy = await upgrades.deployProxy(PancakeStrategyFactory, [ 41 | MIN_EARN_AMT, 42 | false, 43 | [MASTERCHEF, WANT, CAKE, TOKEN0, TOKEN1, ROUTER, farming.address], 44 | EARNED_TO_TOKEN0_PATH, 45 | EARNED_TO_TOKEN1_PATH, 46 | ]); 47 | await pancakeStrategy.deployed(); 48 | currentImplAddress = await getImplementationAddress(ethers.provider, pancakeStrategy.address); 49 | const pancakeStrategyImpl = currentImplAddress; 50 | 51 | const pancakeProxyForDeposit = await upgrades.deployProxy(PancakeProxyForDepositFactory, [ 52 | farming.address, 53 | ROUTER, 54 | ]); 55 | await pancakeProxyForDeposit.deployed(); 56 | currentImplAddress = await getImplementationAddress( 57 | ethers.provider, 58 | pancakeProxyForDeposit.address 59 | ); 60 | const pancakeProxyForDepositImpl = currentImplAddress; 61 | 62 | const addresses = { 63 | hay: HAY, 64 | incentiveVoting: incentiveVoting.address, 65 | incentiveVotingImplementation: incentiveVotingImpl, 66 | farming: farming.address, 67 | farmingImplementation: farmingImpl, 68 | pancakeProxyForDeposit: pancakeProxyForDeposit.address, 69 | pancakeProxyForDepositImplementation: pancakeProxyForDepositImpl, 70 | pancakeStrategy: pancakeStrategy.address, 71 | pancakeStrategyImplementation: pancakeStrategyImpl, 72 | }; 73 | const jsonAddresses = JSON.stringify(addresses); 74 | fs.writeFileSync(`./addresses/${network.name}Addresses.json`, jsonAddresses); 75 | console.log("Addresses saved!"); 76 | 77 | // set Farming 78 | await incentiveVoting.setFarming(farming.address, [WANT], [pancakeStrategy.address]); 79 | console.log("farming setted"); 80 | // set supported token to proxy for farming 81 | await pancakeProxyForDeposit.addSupportedTokens(TOKEN0, TOKEN1, 0); 82 | console.log("proxy for farming config competed"); 83 | 84 | await verifyContract(farmingImpl, []); 85 | await verifyContract(incentiveVotingImpl, []); 86 | await verifyContract(pancakeStrategyImpl, []); 87 | await verifyContract(pancakeProxyForDepositImpl, []); 88 | }; 89 | 90 | main() 91 | .then(() => { 92 | console.log("Success"); 93 | }) 94 | .catch((err) => { 95 | console.log(err); 96 | }); 97 | -------------------------------------------------------------------------------- /scripts/deployAllTestnetDev.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "ethers"; 2 | import fs from "fs"; 3 | import { ethers, network, upgrades } from "hardhat"; 4 | import { verifyContract } from "../helpers/utils"; 5 | // eslint-disable-next-line node/no-extraneous-import 6 | import { getImplementationAddress } from "@openzeppelin/upgrades-core"; 7 | 8 | const { AddressZero } = ethers.constants; 9 | 10 | const START_TIME = BigNumber.from(Math.floor(Date.now() / 1000) + 100); 11 | const HAY = "0xf052cb891c3887acca8741859c6184794c482a8a"; 12 | const MIN_EARN_AMT = "10000000000"; 13 | const MASTERCHEF = AddressZero; 14 | const WANT = "0xb050200e173788D397e1F706E97216E72d24da2E"; 15 | const CAKE = AddressZero; 16 | const TOKEN0 = "0x78867BbEeF44f2326bF8DDd1941a4439382EF2A7"; // BUSD 17 | const TOKEN1 = "0xf052cb891c3887acca8741859c6184794c482a8a"; 18 | const ROUTER = "0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3"; 19 | const EARNED_TO_TOKEN0_PATH: string[] = []; 20 | const EARNED_TO_TOKEN1_PATH: string[] = []; 21 | 22 | const main = async () => { 23 | const FarmingFactory = await ethers.getContractFactory("Farming"); 24 | const IncentiveVotingFactory = await ethers.getContractFactory("IncentiveVoting"); 25 | const PancakeStrategyFactory = await ethers.getContractFactory("PancakeStrategy"); 26 | const PancakeProxyForDepositFactory = await ethers.getContractFactory("PancakeProxyForDeposit"); 27 | 28 | console.log(START_TIME.toString()); 29 | console.log("Start"); 30 | const incentiveVoting = await upgrades.deployProxy(IncentiveVotingFactory, [START_TIME]); 31 | await incentiveVoting.deployed(); 32 | let currentImplAddress = await getImplementationAddress(ethers.provider, incentiveVoting.address); 33 | const incentiveVotingImpl = currentImplAddress; 34 | 35 | const farming = await upgrades.deployProxy(FarmingFactory, [HAY, incentiveVoting.address]); 36 | await farming.deployed(); 37 | currentImplAddress = await getImplementationAddress(ethers.provider, farming.address); 38 | const farmingImpl = currentImplAddress; 39 | 40 | const pancakeStrategy = await upgrades.deployProxy(PancakeStrategyFactory, [ 41 | MIN_EARN_AMT, 42 | false, 43 | [MASTERCHEF, WANT, CAKE, TOKEN0, TOKEN1, ROUTER, farming.address], 44 | EARNED_TO_TOKEN0_PATH, 45 | EARNED_TO_TOKEN1_PATH, 46 | ]); 47 | await pancakeStrategy.deployed(); 48 | currentImplAddress = await getImplementationAddress(ethers.provider, pancakeStrategy.address); 49 | const pancakeStrategyImpl = currentImplAddress; 50 | 51 | const pancakeProxyForDeposit = await upgrades.deployProxy(PancakeProxyForDepositFactory, [ 52 | farming.address, 53 | ROUTER, 54 | ]); 55 | await pancakeProxyForDeposit.deployed(); 56 | currentImplAddress = await getImplementationAddress( 57 | ethers.provider, 58 | pancakeProxyForDeposit.address 59 | ); 60 | const pancakeProxyForDepositImpl = currentImplAddress; 61 | 62 | const addresses = { 63 | hay: HAY, 64 | incentiveVoting: incentiveVoting.address, 65 | incentiveVotingImplementation: incentiveVotingImpl, 66 | farming: farming.address, 67 | farmingImplementation: farmingImpl, 68 | pancakeProxyForDeposit: pancakeProxyForDeposit.address, 69 | pancakeProxyForDepositImplementation: pancakeProxyForDepositImpl, 70 | pancakeStrategy: pancakeStrategy.address, 71 | pancakeStrategyImplementation: pancakeStrategyImpl, 72 | }; 73 | const jsonAddresses = JSON.stringify(addresses); 74 | fs.writeFileSync(`./addresses/${network.name}Addresses.json`, jsonAddresses); 75 | console.log("Addresses saved!"); 76 | 77 | // set Farming 78 | await incentiveVoting.setFarming(farming.address, [WANT], [pancakeStrategy.address]); 79 | console.log("farming setted"); 80 | // set supported token to proxy for farming 81 | await pancakeProxyForDeposit.addSupportedTokens(TOKEN0, TOKEN1, 0); 82 | console.log("proxy for farming config competed"); 83 | 84 | await verifyContract(farmingImpl, []); 85 | await verifyContract(incentiveVotingImpl, []); 86 | await verifyContract(pancakeStrategyImpl, []); 87 | await verifyContract(pancakeProxyForDepositImpl, []); 88 | }; 89 | 90 | main() 91 | .then(() => { 92 | console.log("Success"); 93 | }) 94 | .catch((err) => { 95 | console.log(err); 96 | }); 97 | -------------------------------------------------------------------------------- /scripts/deployAll.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { ethers, network, upgrades } from "hardhat"; 3 | import { verifyContract } from "../helpers/utils"; 4 | // eslint-disable-next-line node/no-extraneous-import 5 | import { getImplementationAddress } from "@openzeppelin/upgrades-core"; 6 | 7 | if (!process.env.START_TIME) { 8 | throw new Error("START_TIME is not setted in .env"); 9 | } 10 | 11 | const START_TIME = process.env.START_TIME; 12 | const HAY = "0x0782b6d8c4551B9760e74c0545a9bCD90bdc41E5"; 13 | const MIN_EARN_AMT = "10000000000"; 14 | const MASTERCHEF = "0xa5f8C5Dbd5F286960b9d90548680aE5ebFf07652"; 15 | const WANT = "0x70c26e9805ec5Df3d4aB0b2a3dF86BBA2231B6c1"; 16 | const CAKE = "0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82"; 17 | const TOKEN0 = "0x0782b6d8c4551B9760e74c0545a9bCD90bdc41E5"; 18 | const TOKEN1 = "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56"; // BUSD 19 | const ROUTER = "0x10ED43C718714eb63d5aA57B78B54704E256024E"; 20 | const EARNED_TO_TOKEN0_PATH = [CAKE, TOKEN1, HAY]; 21 | const EARNED_TO_TOKEN1_PATH = [CAKE, TOKEN1]; 22 | 23 | const main = async () => { 24 | const FarmingFactory = await ethers.getContractFactory("Farming"); 25 | const IncentiveVotingFactory = await ethers.getContractFactory("IncentiveVoting"); 26 | const PancakeStrategyFactory = await ethers.getContractFactory("PancakeStrategy"); 27 | const PancakeProxyForDepositFactory = await ethers.getContractFactory("PancakeProxyForDeposit"); 28 | 29 | console.log(START_TIME.toString()); 30 | console.log("Start"); 31 | const incentiveVoting = await upgrades.deployProxy(IncentiveVotingFactory, [START_TIME]); 32 | await incentiveVoting.deployed(); 33 | let currentImplAddress = await getImplementationAddress(ethers.provider, incentiveVoting.address); 34 | const incentiveVotingImpl = currentImplAddress; 35 | 36 | const farming = await upgrades.deployProxy(FarmingFactory, [HAY, incentiveVoting.address]); 37 | await farming.deployed(); 38 | currentImplAddress = await getImplementationAddress(ethers.provider, farming.address); 39 | const farmingImpl = currentImplAddress; 40 | 41 | const pancakeStrategy = await upgrades.deployProxy(PancakeStrategyFactory, [ 42 | MIN_EARN_AMT, 43 | false, 44 | [MASTERCHEF, WANT, CAKE, TOKEN0, TOKEN1, ROUTER, farming.address], 45 | EARNED_TO_TOKEN0_PATH, 46 | EARNED_TO_TOKEN1_PATH, 47 | ]); 48 | await pancakeStrategy.deployed(); 49 | currentImplAddress = await getImplementationAddress(ethers.provider, pancakeStrategy.address); 50 | const pancakeStrategyImpl = currentImplAddress; 51 | 52 | const pancakeProxyForDeposit = await upgrades.deployProxy(PancakeProxyForDepositFactory, [ 53 | farming.address, 54 | ROUTER, 55 | ]); 56 | await pancakeProxyForDeposit.deployed(); 57 | currentImplAddress = await getImplementationAddress( 58 | ethers.provider, 59 | pancakeProxyForDeposit.address 60 | ); 61 | const pancakeProxyForDepositImpl = currentImplAddress; 62 | 63 | const addresses = { 64 | hay: HAY, 65 | incentiveVoting: incentiveVoting.address, 66 | incentiveVotingImplementation: incentiveVotingImpl, 67 | farming: farming.address, 68 | farmingImplementation: farmingImpl, 69 | pancakeProxyForDeposit: pancakeProxyForDeposit.address, 70 | pancakeProxyForDepositImplementation: pancakeProxyForDepositImpl, 71 | pancakeStrategy: pancakeStrategy.address, 72 | pancakeStrategyImplementation: pancakeStrategyImpl, 73 | }; 74 | const jsonAddresses = JSON.stringify(addresses); 75 | fs.writeFileSync(`./addresses/${network.name}Addresses.json`, jsonAddresses); 76 | console.log("Addresses saved!"); 77 | 78 | // set Farming 79 | await incentiveVoting.setFarming(farming.address, [WANT], [pancakeStrategy.address]); 80 | console.log("farming setted"); 81 | // set supported token to proxy for farming 82 | await pancakeProxyForDeposit.addSupportedTokens(TOKEN0, TOKEN1, 0); 83 | console.log("proxy for farming config competed"); 84 | 85 | await verifyContract(farmingImpl, []); 86 | await verifyContract(incentiveVotingImpl, []); 87 | await verifyContract(pancakeStrategyImpl, []); 88 | await verifyContract(pancakeProxyForDepositImpl, []); 89 | }; 90 | 91 | main() 92 | .then(() => { 93 | console.log("Success"); 94 | }) 95 | .catch((err) => { 96 | console.log(err); 97 | }); 98 | -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # Using Contracts 2 | 3 | ## Token Bonding 4 | 5 | - `bond(, )` - bonds `` of `` and gets "veHelio" for voting 6 | - `bondBatch(, )` - bonds many tokens with one transaction 7 | - `requestUnbond(, )` - request `` of `` to unbond(requester should wait 1 week) 8 | - `requestUnbondBatch(, )` - request unbond for the batch of tokens 9 | - `decreaseUnbondAmount(, )` - decreases requested `` by ``(if you want to decrease all amount(cancel request), you should give `type(uint256).max` as ``) 10 | - `decreaseUnbondAmountBatch(, )` - decreases unbond amount for the batch of tokens 11 | - `unbond()` - unbond requested `` after time elapsed(1 week) 12 | - `unbondBatch()` - unbond for the batch of tokens 13 | - `addToken(, )` - add new bonding `` with ``(18 decimals)(only owner can call this function) 14 | 15 | ## Incentive Voting 16 | 17 | - `vote(, )` - vote for `pid`(pool id) for the `vote` amount 18 | - `addTokenApproval(, , )` - add new token with strategy to farming contract. `` will update all pools information in farming contract. 19 | - `addReward(, )` - adds reward tokens for the `` to contract address to incentivize farming contract users(`` can start from current week) 20 | - `removeReward(, )` remove reward token from contract(started from the next ``) 21 | 22 | ## Farming 23 | 24 | - `poolLength()` - returns length of all pools 25 | - `addPool(, , )` - adds token to the farming contract with the corresponding strategy contract. `` will update all pools information in contract. (can be called only by IncentiveVoting contract) 26 | - `setClaimReceiver()` - sets the reward token receiver address for the user(msg.sender) 27 | - `setBlockThirdPartyActions()` - allow or block other accounts to claim reward for the user(msg.sender). `` variable is boolean type. 28 | - `stakedWantTokens(, )` - returns staked token(pool id token) amount for the user 29 | - `massUpdatePools()` - updates all pools information 30 | - `updatePool(pid)` - updates pool information by pid(pool id) 31 | - `claimableReward(, )` - returns claimable reward amount for the user, for the poolIds array 32 | - `deposit(, , )` - deposits for `pid`(pool id) for the `wantAmount` tokens. `claimRewards` is boolean variable and automatically will claim accumulated rewards if `true` 33 | - `withdraw(, , )` - withdraws deposited `wantAmount` tokens from `pid`(pool id). `claimRewards` is boolean variable and automatically will claim accumulated rewards if `true` 34 | - `withdrawAll(, )` - withdraws all deposited tokens from the pid(pool id) `claimRewards` is boolean variable and automatically will claim accumulated rewards if `true` 35 | - `claim(, )` - Claim pending rewards for one or more pids for a user.(if blockThirdPartiActions is true, than only user can call this function) 36 | - `inCaseTokensGetStuck(, )` if some tokens were stuck in the contract(except rewardToken) owner can get them from contract address 37 | - `emergencyWithdraw()` - Withdraw without caring about rewards. EMERGENCY ONLY. 38 | 39 | ## Usage 40 | 41 | - addresses are located to addresses folder 42 | 43 | ### Steps 44 | 45 | 1. mint fakeHelio/fakeHelioLp tokens to your address by `mintMe(amount)` function 46 | 2. bond fakeHelio/fakeHelioLp tokens to get veHelio for voting power, `TokenBonding.bond()`(for each fakeHelio you get 1 veHelio and for each fakeHelioLp you get 2 veHelio) 47 | 3. you can add rewards tokens(fakeHay) to the incentiveVoting contract by `addReward` function 48 | 4. you can vote for pid(pool id) with veHelio to increase rewards percent for the pool by calling `IncentiveVoting.vote()` function .(in our case pool is only one(with poolId 0)) 49 | 5. you can deposit fakeHay(pool id 0) and accumulate rewards for the week by `Farming.deposit()` 50 | 6. you can withdraw tokens by calling `Farming.withdraw()` 51 | 7. you can claim rewards(fakeHay) by calling `Farming.claim()` 52 | -------------------------------------------------------------------------------- /contracts/interfaces/IPancakeRouter01.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | interface IPancakeRouter01 { 5 | function factory() external pure returns (address); 6 | 7 | // solhint-disable-next-line func-name-mixedcase 8 | function WETH() external pure returns (address); 9 | 10 | function addLiquidity( 11 | address tokenA, 12 | address tokenB, 13 | uint256 amountADesired, 14 | uint256 amountBDesired, 15 | uint256 amountAMin, 16 | uint256 amountBMin, 17 | address to, 18 | uint256 deadline 19 | ) 20 | external 21 | returns ( 22 | uint256 amountA, 23 | uint256 amountB, 24 | uint256 liquidity 25 | ); 26 | 27 | function addLiquidityETH( 28 | address token, 29 | uint256 amountTokenDesired, 30 | uint256 amountTokenMin, 31 | uint256 amountETHMin, 32 | address to, 33 | uint256 deadline 34 | ) 35 | external 36 | payable 37 | returns ( 38 | uint256 amountToken, 39 | uint256 amountETH, 40 | uint256 liquidity 41 | ); 42 | 43 | function removeLiquidity( 44 | address tokenA, 45 | address tokenB, 46 | uint256 liquidity, 47 | uint256 amountAMin, 48 | uint256 amountBMin, 49 | address to, 50 | uint256 deadline 51 | ) external returns (uint256 amountA, uint256 amountB); 52 | 53 | function removeLiquidityETH( 54 | address token, 55 | uint256 liquidity, 56 | uint256 amountTokenMin, 57 | uint256 amountETHMin, 58 | address to, 59 | uint256 deadline 60 | ) external returns (uint256 amountToken, uint256 amountETH); 61 | 62 | function removeLiquidityWithPermit( 63 | address tokenA, 64 | address tokenB, 65 | uint256 liquidity, 66 | uint256 amountAMin, 67 | uint256 amountBMin, 68 | address to, 69 | uint256 deadline, 70 | bool approveMax, 71 | uint8 v, 72 | bytes32 r, 73 | bytes32 s 74 | ) external returns (uint256 amountA, uint256 amountB); 75 | 76 | function removeLiquidityETHWithPermit( 77 | address token, 78 | uint256 liquidity, 79 | uint256 amountTokenMin, 80 | uint256 amountETHMin, 81 | address to, 82 | uint256 deadline, 83 | bool approveMax, 84 | uint8 v, 85 | bytes32 r, 86 | bytes32 s 87 | ) external returns (uint256 amountToken, uint256 amountETH); 88 | 89 | function swapExactTokensForTokens( 90 | uint256 amountIn, 91 | uint256 amountOutMin, 92 | address[] calldata path, 93 | address to, 94 | uint256 deadline 95 | ) external returns (uint256[] memory amounts); 96 | 97 | function swapTokensForExactTokens( 98 | uint256 amountOut, 99 | uint256 amountInMax, 100 | address[] calldata path, 101 | address to, 102 | uint256 deadline 103 | ) external returns (uint256[] memory amounts); 104 | 105 | function swapExactETHForTokens( 106 | uint256 amountOutMin, 107 | address[] calldata path, 108 | address to, 109 | uint256 deadline 110 | ) external payable returns (uint256[] memory amounts); 111 | 112 | function swapTokensForExactETH( 113 | uint256 amountOut, 114 | uint256 amountInMax, 115 | address[] calldata path, 116 | address to, 117 | uint256 deadline 118 | ) external returns (uint256[] memory amounts); 119 | 120 | function swapExactTokensForETH( 121 | uint256 amountIn, 122 | uint256 amountOutMin, 123 | address[] calldata path, 124 | address to, 125 | uint256 deadline 126 | ) external returns (uint256[] memory amounts); 127 | 128 | function swapETHForExactTokens( 129 | uint256 amountOut, 130 | address[] calldata path, 131 | address to, 132 | uint256 deadline 133 | ) external payable returns (uint256[] memory amounts); 134 | 135 | function quote( 136 | uint256 amountA, 137 | uint256 reserveA, 138 | uint256 reserveB 139 | ) external pure returns (uint256 amountB); 140 | 141 | function getAmountOut( 142 | uint256 amountIn, 143 | uint256 reserveIn, 144 | uint256 reserveOut 145 | ) external pure returns (uint256 amountOut); 146 | 147 | function getAmountIn( 148 | uint256 amountOut, 149 | uint256 reserveIn, 150 | uint256 reserveOut 151 | ) external pure returns (uint256 amountIn); 152 | 153 | function getAmountsOut(uint256 amountIn, address[] calldata path) 154 | external 155 | view 156 | returns (uint256[] memory amounts); 157 | 158 | function getAmountsIn(uint256 amountOut, address[] calldata path) 159 | external 160 | view 161 | returns (uint256[] memory amounts); 162 | } 163 | -------------------------------------------------------------------------------- /contracts/PancakeStrategy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; 6 | import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; 7 | import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; 8 | import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 9 | 10 | import { IStrategy } from "./interfaces/IStrategy.sol"; 11 | 12 | // solhint-disable max-states-count 13 | contract PancakeStrategy is 14 | IStrategy, 15 | OwnableUpgradeable, 16 | ReentrancyGuardUpgradeable, 17 | PausableUpgradeable 18 | { 19 | using SafeERC20Upgradeable for IERC20Upgradeable; 20 | 21 | event AutoharvestChanged(bool value); 22 | event MinEarnAmountChanged(uint256 indexed oldAmount, uint256 indexed newAmount); 23 | 24 | uint256 public pid; 25 | address public farmContractAddress; 26 | address public want; 27 | address public cake; 28 | address public token0; 29 | address public token1; 30 | address public router; 31 | address public helioFarming; 32 | 33 | bool public enableAutoHarvest; 34 | 35 | address[] public earnedToToken0Path; 36 | address[] public earnedToToken1Path; 37 | 38 | uint256 internal _wantLockedTotal; 39 | uint256 public sharesTotal; 40 | 41 | uint256 public minEarnAmount; 42 | uint256 public constant MIN_EARN_AMOUNT_LL = 10**10; 43 | 44 | uint256 public slippageFactor; 45 | uint256 public constant SLIPPAGE_FACTOR_UL = 995; 46 | uint256 public constant SLIPPAGE_FACTOR_MAX = 1000; 47 | 48 | modifier onlyHelioFarming() { 49 | require(msg.sender == helioFarming, "!helio Farming"); 50 | _; 51 | } 52 | 53 | function initialize( 54 | // uint256 _pid, 55 | uint256 _minEarnAmount, 56 | bool _enableAutoHarvest, 57 | address[] memory _addresses, 58 | // 0 address _farmContractAddress, 59 | // 1 address _want, 60 | // 2 address _cake, 61 | // 3 address _token0, 62 | // 4 address _token1, 63 | // 5 address _router, 64 | // 6 address _helioFarming, 65 | address[] memory _earnedToToken0Path, 66 | address[] memory _earnedToToken1Path 67 | ) public initializer { 68 | __Ownable_init(); 69 | __ReentrancyGuard_init(); 70 | __Pausable_init(); 71 | require(_minEarnAmount >= MIN_EARN_AMOUNT_LL, "min earn amount is too low"); 72 | slippageFactor = 950; 73 | // pid = _pid; 74 | minEarnAmount = _minEarnAmount; 75 | farmContractAddress = _addresses[0]; 76 | want = _addresses[1]; 77 | cake = _addresses[2]; 78 | token0 = _addresses[3]; 79 | token1 = _addresses[4]; 80 | router = _addresses[5]; 81 | helioFarming = _addresses[6]; 82 | enableAutoHarvest = _enableAutoHarvest; 83 | earnedToToken0Path = _earnedToToken0Path; 84 | earnedToToken1Path = _earnedToToken1Path; 85 | } 86 | 87 | // Receives new deposits from user 88 | function deposit(address, uint256 _wantAmt) 89 | public 90 | virtual 91 | onlyHelioFarming 92 | whenNotPaused 93 | returns (uint256) 94 | { 95 | IERC20Upgradeable(want).safeTransferFrom(address(msg.sender), address(this), _wantAmt); 96 | 97 | uint256 sharesAdded = _wantAmt; 98 | sharesTotal += sharesAdded; 99 | 100 | return sharesAdded; 101 | } 102 | 103 | function withdraw(address, uint256 _wantAmt) 104 | public 105 | virtual 106 | onlyHelioFarming 107 | nonReentrant 108 | returns (uint256) 109 | { 110 | require(_wantAmt > 0, "_wantAmt <= 0"); 111 | 112 | uint256 sharesRemoved = _wantAmt; 113 | uint256 sharesTotalLocal = sharesTotal; 114 | if (sharesRemoved > sharesTotalLocal) { 115 | sharesRemoved = sharesTotalLocal; 116 | } 117 | sharesTotal = sharesTotalLocal - sharesRemoved; 118 | 119 | uint256 wantAmt = IERC20Upgradeable(want).balanceOf(address(this)); 120 | if (_wantAmt > wantAmt) { 121 | _wantAmt = wantAmt; 122 | } 123 | 124 | IERC20Upgradeable(want).safeTransfer(helioFarming, _wantAmt); 125 | 126 | return sharesRemoved; 127 | } 128 | 129 | function inCaseTokensGetStuck( 130 | address _token, 131 | uint256 _amount, 132 | address _to 133 | ) public virtual onlyOwner { 134 | require(_token != cake, "!safe"); 135 | require(_token != want, "!safe"); 136 | IERC20Upgradeable(_token).safeTransfer(_to, _amount); 137 | } 138 | 139 | function pause() public virtual onlyOwner { 140 | _pause(); 141 | } 142 | 143 | function unpause() public virtual onlyOwner { 144 | _unpause(); 145 | } 146 | 147 | function wantLockedTotal() external view virtual override returns (uint256) { 148 | return sharesTotal; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /test/use-forking/4_PancakeStrategy.oldTest.ts: -------------------------------------------------------------------------------- 1 | import chai, { assert, expect } from "chai"; 2 | import chaiAsPromised from "chai-as-promised"; 3 | import { ethers, upgrades } from "hardhat"; 4 | import { BigNumber } from "ethers"; 5 | import { solidity } from "ethereum-waffle"; 6 | 7 | import { 8 | advanceBlock, 9 | advanceBlockAndTime, 10 | advanceTime, 11 | daysToSeconds, 12 | getNextTimestampDivisibleBy, 13 | setTimestamp, 14 | } from "../../helpers/utils"; 15 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 16 | import { 17 | IERC20, 18 | IPancakeRouter02, 19 | IWBNB, 20 | PancakeStrategy, 21 | StrategyMock, 22 | } from "../../typechain-types"; 23 | import NetworkSnapshotter from "../../helpers/NetworkSnapshotter"; 24 | 25 | const { AddressZero, MaxUint256 } = ethers.constants; 26 | 27 | chai.use(solidity); 28 | chai.use(chaiAsPromised); 29 | 30 | const week = daysToSeconds(BigNumber.from(7)); 31 | const ten = BigNumber.from(10); 32 | const tenPow18 = ten.pow(18); 33 | 34 | describe("Strategy", () => { 35 | let deployer: SignerWithAddress; 36 | let helioFarming: SignerWithAddress; 37 | let signer2: SignerWithAddress; 38 | let strategy: PancakeStrategy; 39 | let token0: IERC20; 40 | let token1: IERC20; 41 | let want: IERC20; 42 | let cake: IERC20; 43 | let wbnb: IWBNB; 44 | let router: IPancakeRouter02; 45 | 46 | const networkSnapshotter = new NetworkSnapshotter(); 47 | 48 | const minEarnAmount = ten.pow(5); 49 | 50 | before("setup strategy contract", async () => { 51 | [deployer, helioFarming, signer2] = await ethers.getSigners(); 52 | // TODO: should be researched 53 | const pid = BigNumber.from(3); 54 | const enableAutoHarvest = true; 55 | const masterChef = "0xa5f8C5Dbd5F286960b9d90548680aE5ebFf07652"; 56 | token0 = await ethers.getContractAt("IERC20", "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"); // WBNB 57 | token1 = await ethers.getContractAt("IERC20", "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56"); // BUSD 58 | want = await ethers.getContractAt("IERC20", "0x58F876857a02D6762E0101bb5C46A8c1ED44Dc16"); 59 | cake = await ethers.getContractAt("IERC20", "0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82"); 60 | wbnb = await ethers.getContractAt("IWBNB", "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c"); 61 | router = await ethers.getContractAt( 62 | "IPancakeRouter02", 63 | "0x10ED43C718714eb63d5aA57B78B54704E256024E" 64 | ); 65 | const addresses = [ 66 | masterChef, 67 | want.address, 68 | cake.address, 69 | token0.address, 70 | token1.address, 71 | router.address, 72 | helioFarming.address, 73 | ]; 74 | const earnedToToken0Path = [cake.address, token0.address]; 75 | const earnedToToken1Path = [cake.address, token1.address]; 76 | 77 | const Strategy = await ethers.getContractFactory("PancakeStrategy"); 78 | strategy = (await upgrades.deployProxy(Strategy, [ 79 | pid, 80 | minEarnAmount, 81 | enableAutoHarvest, 82 | addresses, 83 | earnedToToken0Path, 84 | earnedToToken1Path, 85 | ])) as PancakeStrategy; 86 | await strategy.deployed(); 87 | console.log("local deployments end"); 88 | const amount = BigNumber.from("1000").mul(tenPow18); 89 | await wbnb.connect(helioFarming).deposit({ value: amount }); 90 | console.log("wbnb deposit end"); 91 | await wbnb.connect(helioFarming).approve(router.address, MaxUint256); 92 | await token1.connect(helioFarming).approve(router.address, MaxUint256); 93 | await router 94 | .connect(helioFarming) 95 | .swapExactTokensForTokens( 96 | amount.div(2), 97 | 0, 98 | [token0.address, token1.address], 99 | helioFarming.address, 100 | Math.floor(Date.now() / 1000) + 10000000 101 | ); 102 | console.log("swap half bnb to busd works"); 103 | await router 104 | .connect(helioFarming) 105 | .addLiquidity( 106 | token0.address, 107 | token1.address, 108 | amount.div(2), 109 | amount.div(2), 110 | 0, 111 | 0, 112 | helioFarming.address, 113 | Math.floor(Date.now() / 1000) + 10000000 114 | ); 115 | console.log("liquidity added"); 116 | const balanceLP = await want.balanceOf(helioFarming.address); 117 | console.log("balance of lp is", balanceLP.toString()); 118 | 119 | // snapshot a network 120 | await networkSnapshotter.firstSnapshot(); 121 | }); 122 | 123 | afterEach("revert", async () => await networkSnapshotter.revert()); 124 | 125 | it("deposit", async () => { 126 | await want.connect(helioFarming).approve(strategy.address, MaxUint256); 127 | const amount = BigNumber.from(10); 128 | await strategy.connect(helioFarming).deposit(AddressZero, amount.mul(tenPow18)); 129 | // await advanceBlockAndTime(100, 3600); 130 | await advanceBlock(100); 131 | console.log("end advance block"); 132 | await strategy.connect(helioFarming).deposit(AddressZero, amount.mul(tenPow18)); 133 | console.log("end deposit"); 134 | 135 | const balanceLPBefore = await want.balanceOf(helioFarming.address); 136 | console.log("Balance before is", balanceLPBefore.toString()); 137 | await strategy.connect(helioFarming).withdraw(AddressZero, amount.mul(2).mul(tenPow18)); 138 | const balanceLPAfter = await want.balanceOf(helioFarming.address); 139 | console.log("Balance after is", balanceLPAfter.toString()); 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /contracts/ProxyUpgrades/TransparentUpgradeableProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.7.0) (proxy/transparent/TransparentUpgradeableProxy.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 7 | 8 | /** 9 | * @dev This contract implements a proxy that is upgradeable by an admin. 10 | * 11 | * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector 12 | * clashing], which can potentially be used in an attack, this contract uses the 13 | * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two 14 | * things that go hand in hand: 15 | * 16 | * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if 17 | * that call matches one of the admin functions exposed by the proxy itself. 18 | * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the 19 | * implementation. If the admin tries to call a function on the implementation it will fail with an error that says 20 | * "admin cannot fallback to proxy target". 21 | * 22 | * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing 23 | * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due 24 | * to sudden errors when trying to call a function from the proxy implementation. 25 | * 26 | * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, 27 | * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. 28 | */ 29 | contract TransparentUpgradeableProxy is ERC1967Proxy { 30 | /** 31 | * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and 32 | * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}. 33 | */ 34 | constructor( 35 | address _logic, 36 | address admin_, 37 | bytes memory _data 38 | ) payable ERC1967Proxy(_logic, _data) { 39 | _changeAdmin(admin_); 40 | } 41 | 42 | /** 43 | * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin. 44 | */ 45 | modifier ifAdmin() { 46 | if (msg.sender == _getAdmin()) { 47 | _; 48 | } else { 49 | _fallback(); 50 | } 51 | } 52 | 53 | /** 54 | * @dev Returns the current admin. 55 | * 56 | * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}. 57 | * 58 | * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the 59 | * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. 60 | * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` 61 | */ 62 | function admin() external ifAdmin returns (address admin_) { 63 | admin_ = _getAdmin(); 64 | } 65 | 66 | /** 67 | * @dev Returns the current implementation. 68 | * 69 | * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}. 70 | * 71 | * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the 72 | * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. 73 | * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` 74 | */ 75 | function implementation() external ifAdmin returns (address implementation_) { 76 | implementation_ = _implementation(); 77 | } 78 | 79 | /** 80 | * @dev Changes the admin of the proxy. 81 | * 82 | * Emits an {AdminChanged} event. 83 | * 84 | * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}. 85 | */ 86 | function changeAdmin(address newAdmin) external virtual ifAdmin { 87 | _changeAdmin(newAdmin); 88 | } 89 | 90 | /** 91 | * @dev Upgrade the implementation of the proxy. 92 | * 93 | * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}. 94 | */ 95 | function upgradeTo(address newImplementation) external ifAdmin { 96 | _upgradeToAndCall(newImplementation, bytes(""), false); 97 | } 98 | 99 | /** 100 | * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified 101 | * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the 102 | * proxied contract. 103 | * 104 | * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}. 105 | */ 106 | function upgradeToAndCall(address newImplementation, bytes calldata data) 107 | external 108 | payable 109 | ifAdmin 110 | { 111 | _upgradeToAndCall(newImplementation, data, true); 112 | } 113 | 114 | /** 115 | * @dev Returns the current admin. 116 | */ 117 | function _admin() internal view virtual returns (address) { 118 | return _getAdmin(); 119 | } 120 | 121 | /** 122 | * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}. 123 | */ 124 | function _beforeFallback() internal virtual override { 125 | require( 126 | msg.sender != _getAdmin(), 127 | "TransparentUpgradeableProxy: admin cannot fallback to proxy target" 128 | ); 129 | super._beforeFallback(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /test/use-forking/4_StableCoinStrategyCurve.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { ethers, upgrades } from "hardhat"; 3 | import { BigNumber } from "ethers"; 4 | 5 | import { advanceBlock } from "../../helpers/utils"; 6 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 7 | import { 8 | IERC20, 9 | IPancakeRouter02, 10 | IStableSwap, 11 | IWBNB, 12 | StableCoinStrategyCurve, 13 | } from "../../typechain-types"; 14 | import NetworkSnapshotter from "../../helpers/NetworkSnapshotter"; 15 | 16 | const { AddressZero, MaxUint256 } = ethers.constants; 17 | 18 | const ten = BigNumber.from(10); 19 | const tenPow18 = ten.pow(18); 20 | 21 | describe("Strategy", () => { 22 | let deployer: SignerWithAddress; 23 | let helioFarming: SignerWithAddress; 24 | let signer2: SignerWithAddress; 25 | let strategy: StableCoinStrategyCurve; 26 | let stableSwap: IStableSwap; 27 | let token0: IERC20; 28 | let token1: IERC20; 29 | let want: IERC20; 30 | let cake: IERC20; 31 | let wbnb: IWBNB; 32 | let router: IPancakeRouter02; 33 | 34 | const networkSnapshotter = new NetworkSnapshotter(); 35 | 36 | const minEarnAmount = "1000000000000"; 37 | 38 | before("setup strategy contract", async () => { 39 | [deployer, helioFarming, signer2] = await ethers.getSigners(); 40 | // TODO: should be researched 41 | const enableAutoHarvest = true; 42 | const pid = "121"; 43 | const I0 = "0"; 44 | const I1 = "1"; 45 | const masterChef = "0xa5f8C5Dbd5F286960b9d90548680aE5ebFf07652"; 46 | token0 = await ethers.getContractAt("IERC20", "0x0782b6d8c4551B9760e74c0545a9bCD90bdc41E5"); // WBNB 47 | token1 = await ethers.getContractAt("IERC20", "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56"); // BUSD 48 | want = await ethers.getContractAt("IERC20", "0xB6040A9F294477dDAdf5543a24E5463B8F2423Ae"); 49 | cake = await ethers.getContractAt("IERC20", "0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82"); 50 | wbnb = await ethers.getContractAt("IWBNB", "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c"); 51 | router = await ethers.getContractAt( 52 | "IPancakeRouter02", 53 | "0x10ED43C718714eb63d5aA57B78B54704E256024E" 54 | ); 55 | stableSwap = await ethers.getContractAt( 56 | "IStableSwap", 57 | "0x49079D07ef47449aF808A4f36c2a8dEC975594eC" 58 | ); 59 | const addresses = [ 60 | masterChef, 61 | want.address, 62 | cake.address, 63 | token0.address, 64 | token1.address, 65 | router.address, 66 | stableSwap.address, 67 | helioFarming.address, 68 | ]; 69 | const EARNED_TO_TOKEN1_PATH = [cake.address, token1.address]; 70 | 71 | const Strategy = await ethers.getContractFactory("StableCoinStrategyCurve"); 72 | strategy = (await upgrades.deployProxy(Strategy, [ 73 | pid, 74 | I0, 75 | I1, 76 | minEarnAmount, 77 | enableAutoHarvest, 78 | addresses, 79 | EARNED_TO_TOKEN1_PATH, 80 | ])) as StableCoinStrategyCurve; 81 | await strategy.deployed(); 82 | console.log("local deployments end"); 83 | 84 | const amount = tenPow18.mul(200); 85 | await wbnb.connect(helioFarming).deposit({ value: tenPow18.mul(200) }); 86 | console.log("wbnb deposit end"); 87 | await wbnb.connect(helioFarming).approve(router.address, MaxUint256); 88 | await token0.connect(helioFarming).approve(router.address, MaxUint256); 89 | await token1.connect(helioFarming).approve(router.address, MaxUint256); 90 | await token0.connect(helioFarming).approve(stableSwap.address, MaxUint256); 91 | await token1.connect(helioFarming).approve(stableSwap.address, MaxUint256); 92 | await router 93 | .connect(helioFarming) 94 | .swapExactTokensForTokens( 95 | amount, 96 | 0, 97 | [wbnb.address, token1.address], 98 | helioFarming.address, 99 | Math.floor(Date.now() / 1000) + 10000000 100 | ); 101 | let busdBalance = await token1.balanceOf(helioFarming.address); 102 | console.log("swap whole amount to busd works"); 103 | await stableSwap.connect(helioFarming).exchange(1, 0, busdBalance.div(2), 0); 104 | console.log("swap half busd to hay works"); 105 | busdBalance = await token1.balanceOf(helioFarming.address); 106 | const hayBalance = await token0.balanceOf(helioFarming.address); 107 | await stableSwap.connect(helioFarming).add_liquidity([hayBalance, busdBalance], 0); 108 | const balanceLP = await want.balanceOf(helioFarming.address); 109 | console.log("balance of lp is", balanceLP.toString()); 110 | 111 | // snapshot a network 112 | await networkSnapshotter.firstSnapshot(); 113 | }); 114 | 115 | afterEach("revert", async () => await networkSnapshotter.revert()); 116 | 117 | it("deposit", async () => { 118 | console.log("Success!"); 119 | await want.connect(helioFarming).approve(strategy.address, MaxUint256); 120 | const amount = BigNumber.from(10); 121 | await strategy.connect(helioFarming).deposit(AddressZero, amount.mul(tenPow18)); 122 | // await advanceBlockAndTime(100, 3600); 123 | await advanceBlock(10000); 124 | console.log("end advance block"); 125 | await strategy.connect(helioFarming).deposit(AddressZero, amount.mul(tenPow18)); 126 | console.log("end deposit"); 127 | 128 | const balanceLPBefore = await want.balanceOf(helioFarming.address); 129 | console.log("Balance before is", balanceLPBefore.toString()); 130 | await strategy 131 | .connect(helioFarming) 132 | .withdraw(AddressZero, amount.mul(2).mul(tenPow18).add(ten.pow(10))); 133 | const balanceLPAfter = await want.balanceOf(helioFarming.address); 134 | console.log("Balance after is", balanceLPAfter.toString()); 135 | }); 136 | }); 137 | -------------------------------------------------------------------------------- /contracts/upgrades/PancakeStrategyV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 5 | import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; 6 | 7 | import { IPancakeSwapFarm } from "../interfaces/IPancakeSwapFarm.sol"; 8 | import { IPancakeRouter02 } from "../interfaces/IPancakeRouter02.sol"; 9 | import { PancakeStrategy } from "../PancakeStrategy.sol"; 10 | 11 | contract PancakeStrategyV2 is PancakeStrategy { 12 | using SafeERC20Upgradeable for IERC20Upgradeable; 13 | 14 | // Receives new deposits from user 15 | function deposit(address, uint256 _wantAmt) 16 | public 17 | virtual 18 | override 19 | onlyHelioFarming 20 | whenNotPaused 21 | returns (uint256) 22 | { 23 | if (enableAutoHarvest) { 24 | _harvest(); 25 | } 26 | IERC20Upgradeable(want).safeTransferFrom(address(msg.sender), address(this), _wantAmt); 27 | 28 | uint256 sharesAdded = _wantAmt; 29 | 30 | uint256 sharesTotalLocal = sharesTotal; 31 | uint256 wantLockedTotalLocal = _wantLockedTotal; 32 | 33 | if (wantLockedTotalLocal > 0 && sharesTotalLocal > 0) { 34 | sharesAdded = (_wantAmt * sharesTotalLocal) / wantLockedTotalLocal; 35 | } 36 | sharesTotal = sharesTotalLocal + sharesAdded; 37 | 38 | _farm(); 39 | 40 | return sharesAdded; 41 | } 42 | 43 | function withdraw(address, uint256 _wantAmt) 44 | public 45 | virtual 46 | override 47 | onlyHelioFarming 48 | nonReentrant 49 | returns (uint256) 50 | { 51 | require(_wantAmt > 0, "_wantAmt <= 0"); 52 | 53 | if (enableAutoHarvest) { 54 | _harvest(); 55 | } 56 | 57 | uint256 sharesRemoved = (_wantAmt * sharesTotal) / _wantLockedTotal; 58 | 59 | uint256 sharesTotalLocal = sharesTotal; 60 | if (sharesRemoved > sharesTotalLocal) { 61 | sharesRemoved = sharesTotalLocal; 62 | } 63 | sharesTotal = sharesTotalLocal - sharesRemoved; 64 | 65 | _unfarm(_wantAmt); 66 | 67 | uint256 wantAmt = IERC20Upgradeable(want).balanceOf(address(this)); 68 | if (_wantAmt > wantAmt) { 69 | _wantAmt = wantAmt; 70 | } 71 | 72 | if (_wantLockedTotal < _wantAmt) { 73 | _wantAmt = _wantLockedTotal; 74 | } 75 | 76 | _wantLockedTotal -= _wantAmt; 77 | 78 | IERC20Upgradeable(want).safeTransfer(helioFarming, _wantAmt); 79 | 80 | return sharesRemoved; 81 | } 82 | 83 | function farm() public virtual nonReentrant { 84 | _farm(); 85 | } 86 | 87 | function _farm() internal virtual { 88 | uint256 wantAmt = IERC20Upgradeable(want).balanceOf(address(this)); 89 | _wantLockedTotal += wantAmt; 90 | IERC20Upgradeable(want).safeIncreaseAllowance(farmContractAddress, wantAmt); 91 | 92 | IPancakeSwapFarm(farmContractAddress).deposit(pid, wantAmt); 93 | } 94 | 95 | function _unfarm(uint256 _wantAmt) internal virtual { 96 | IPancakeSwapFarm(farmContractAddress).withdraw(pid, _wantAmt); 97 | } 98 | 99 | // 1. Harvest farm tokens 100 | // 2. Converts farm tokens into want tokens 101 | // 3. Deposits want tokens 102 | function harvest() public virtual nonReentrant whenNotPaused { 103 | _harvest(); 104 | } 105 | 106 | // 1. Harvest farm tokens 107 | // 2. Converts farm tokens into want tokens 108 | // 3. Deposits want tokens 109 | function _harvest() internal virtual { 110 | // Harvest farm tokens 111 | _unfarm(0); 112 | 113 | // Converts farm tokens into want tokens 114 | uint256 earnedAmt = IERC20Upgradeable(cake).balanceOf(address(this)); 115 | 116 | IERC20Upgradeable(cake).safeApprove(router, 0); 117 | IERC20Upgradeable(cake).safeIncreaseAllowance(router, earnedAmt); 118 | 119 | if (earnedAmt < minEarnAmount) { 120 | return; 121 | } 122 | 123 | if (cake != token0) { 124 | // Swap half earned to token0 125 | _safeSwap( 126 | router, 127 | earnedAmt / 2, 128 | slippageFactor, 129 | earnedToToken0Path, 130 | address(this), 131 | block.timestamp + 600 132 | ); 133 | } 134 | 135 | if (cake != token1) { 136 | // Swap half earned to token1 137 | _safeSwap( 138 | router, 139 | earnedAmt / 2, 140 | slippageFactor, 141 | earnedToToken1Path, 142 | address(this), 143 | block.timestamp + 600 144 | ); 145 | } 146 | 147 | // Get want tokens, ie. add liquidity 148 | uint256 token0Amt = IERC20Upgradeable(token0).balanceOf(address(this)); 149 | uint256 token1Amt = IERC20Upgradeable(token1).balanceOf(address(this)); 150 | if (token0Amt > 0 && token1Amt > 0) { 151 | IERC20Upgradeable(token0).safeIncreaseAllowance(router, token0Amt); 152 | IERC20Upgradeable(token1).safeIncreaseAllowance(router, token1Amt); 153 | IPancakeRouter02(router).addLiquidity( 154 | token0, 155 | token1, 156 | token0Amt, 157 | token1Amt, 158 | 0, 159 | 0, 160 | address(this), 161 | block.timestamp + 600 162 | ); 163 | } 164 | 165 | _farm(); 166 | } 167 | 168 | function _safeSwap( 169 | address _uniRouterAddress, 170 | uint256 _amountIn, 171 | uint256 _slippageFactor, 172 | address[] memory _path, 173 | address _to, 174 | uint256 _deadline 175 | ) internal virtual { 176 | uint256[] memory amounts = IPancakeRouter02(_uniRouterAddress).getAmountsOut(_amountIn, _path); 177 | uint256 amountOut = amounts[amounts.length - 1]; 178 | 179 | IPancakeRouter02(_uniRouterAddress).swapExactTokensForTokensSupportingFeeOnTransferTokens( 180 | _amountIn, 181 | (amountOut * _slippageFactor) / SLIPPAGE_FACTOR_MAX, 182 | _path, 183 | _to, 184 | _deadline 185 | ); 186 | } 187 | 188 | function setAutoHarvest(bool _value) external onlyOwner { 189 | enableAutoHarvest = _value; 190 | emit AutoharvestChanged(_value); 191 | } 192 | 193 | function setSlippageFactor(uint256 _slippageFactor) external onlyOwner { 194 | require(_slippageFactor <= SLIPPAGE_FACTOR_UL, "slippageFactor too high"); 195 | slippageFactor = _slippageFactor; 196 | } 197 | 198 | function setMinEarnAmount(uint256 _minEarnAmount) external onlyOwner { 199 | require(_minEarnAmount >= MIN_EARN_AMOUNT_LL, "min earn amount is too low"); 200 | emit MinEarnAmountChanged(minEarnAmount, _minEarnAmount); 201 | minEarnAmount = _minEarnAmount; 202 | } 203 | 204 | function setPid(uint256 _pid) external onlyOwner { 205 | require(pid == 0, "pid already setted"); 206 | pid = _pid; 207 | } 208 | 209 | function wantLockedTotal() external view virtual override returns (uint256) { 210 | return _wantLockedTotal; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /contracts/IncentiveVoting.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 5 | import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 6 | import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 7 | 8 | import { IFarming } from "./interfaces/IFarming.sol"; 9 | import { ITokenBonding } from "./interfaces/ITokenBonding.sol"; 10 | import { IIncentiveVoting } from "./interfaces/IIncentiveVoting.sol"; 11 | 12 | contract IncentiveVoting is IIncentiveVoting, Initializable, OwnableUpgradeable { 13 | struct Vote { 14 | address token; 15 | uint256 votes; 16 | } 17 | 18 | // pid -> week -> votes received 19 | mapping(uint256 => uint256[65535]) public pidVotes; 20 | 21 | // user -> week -> votes used 22 | mapping(address => uint256[65535]) public userVotes; 23 | 24 | // user -> pid -> week -> votes for pool 25 | mapping(address => mapping(uint256 => uint256[65535])) public userPidVotes; 26 | 27 | // week -> total votes used 28 | uint256[65535] public totalVotes; 29 | 30 | // week -> addedRewards 31 | uint256[65535] public totalRewards; 32 | 33 | uint256 internal constant WEEK = 1 weeks; 34 | uint256 public override startTime; 35 | 36 | ITokenBonding public tokenBonding; 37 | IFarming public farming; 38 | 39 | mapping(uint256 => address) public tokenByPid; 40 | uint256[] public approvedPids; 41 | 42 | IERC20Upgradeable public rewardToken; 43 | 44 | event VotedForIncentives( 45 | address indexed voter, 46 | uint256[] pids, 47 | uint256[] votes, 48 | uint256 userVotesUsed, 49 | uint256 totalUserVotes 50 | ); 51 | 52 | event RewardChanged( 53 | address indexed user, 54 | uint256 indexed week, 55 | int256 amount, 56 | uint256 totalAmount 57 | ); 58 | 59 | function initialize(uint256 startTime_) public initializer { 60 | require(startTime_ > block.timestamp, "!epoch week"); 61 | __Ownable_init(); 62 | startTime = startTime_; 63 | } 64 | 65 | function setFarming( 66 | IFarming _farming, 67 | address[] calldata _initialApprovedTokens, 68 | address[] calldata _strategies 69 | ) external virtual returns (uint256[] memory) { 70 | require(address(farming) == address(0), "farming address can be set only once"); 71 | uint256 length = _initialApprovedTokens.length; 72 | require(length == _strategies.length, "lengths are not equal"); 73 | uint256[] memory _pids = new uint256[](length); 74 | farming = _farming; 75 | rewardToken = _farming.rewardToken(); 76 | rewardToken.approve(address(_farming), type(uint256).max); 77 | for (uint256 i = 0; i < _initialApprovedTokens.length; i++) { 78 | address token = _initialApprovedTokens[i]; 79 | tokenByPid[i] = token; 80 | approvedPids.push(i); 81 | _pids[i] = _farming.addPool(token, _strategies[i], false); 82 | } 83 | return _pids; 84 | } 85 | 86 | function approvedPoolsLength() external view returns (uint256) { 87 | return approvedPids.length; 88 | } 89 | 90 | function getWeek() public view virtual returns (uint256) { 91 | if (startTime > block.timestamp) return 0; 92 | return (block.timestamp - startTime) / WEEK; 93 | } 94 | 95 | /** 96 | @notice Get data on the current votes made in the active week 97 | @return _totalVotes Total number of votes this week for all pools 98 | @return _voteData Dynamic array of (token address, votes for token) 99 | */ 100 | function getVotes(uint256 _week) 101 | external 102 | view 103 | virtual 104 | returns (uint256 _totalVotes, Vote[] memory _voteData) 105 | { 106 | _voteData = new Vote[](approvedPids.length); 107 | for (uint256 i = 0; i < _voteData.length; i++) { 108 | address token = tokenByPid[i]; 109 | _voteData[i] = Vote({ token: token, votes: pidVotes[i][_week] }); 110 | } 111 | return (totalVotes[_week], _voteData); 112 | } 113 | 114 | /** 115 | @notice Get data on current votes `_user` has made in the active week 116 | @return _totalVotes Total number of votes from `_user` this week for all pools 117 | @return _voteData Dynamic array of (token address, votes for token) 118 | */ 119 | function getUserVotes(address _user, uint256 _week) 120 | external 121 | view 122 | virtual 123 | returns (uint256 _totalVotes, Vote[] memory _voteData) 124 | { 125 | _voteData = new Vote[](approvedPids.length); 126 | for (uint256 i = 0; i < _voteData.length; i++) { 127 | address token = tokenByPid[i]; 128 | _voteData[i] = Vote({ token: token, votes: userPidVotes[_user][i][_week] }); 129 | } 130 | return (userVotes[_user][_week], _voteData); 131 | } 132 | 133 | /** 134 | @notice Get the amount of unused votes for for the current week being voted on 135 | @param _user Address to query 136 | @return uint Amount of unused votes 137 | */ 138 | function availableVotes(address _user) external view virtual returns (uint256) { 139 | if (_user == owner()) { 140 | return type(uint256).max; 141 | } 142 | return 0; 143 | } 144 | 145 | /** 146 | @notice Allocate votes toward LP tokens to receive emissions in the following week 147 | @dev A user may vote as many times as they like within a week, so long as their total 148 | available votes are not exceeded. If they receive additional votes by locking more 149 | tokens within `tokenBonding`, they can vote immediately. 150 | 151 | Votes can only be added - not modified or removed. Votes only apply to the 152 | following week - they do not carry over. A user must resubmit their vote each 153 | week. 154 | @param _pids List of pool ids of LP tokens to vote for 155 | @param _votes Votes to allocate to `_tokens`. Values are additive, they do 156 | not include previous votes. For example, if you have already 157 | allocated 100 votes and wish to allocate a total of 300, 158 | the vote amount should be given as 200. 159 | */ 160 | function vote(uint256[] calldata _pids, uint256[] calldata _votes) external virtual onlyOwner { 161 | require(_pids.length == _votes.length, "Input length mismatch"); 162 | 163 | // update rewards per second, if required 164 | uint256 week = getWeek(); 165 | 166 | // update accounting for this week's votes 167 | uint256 usedVotes = userVotes[msg.sender][week]; 168 | for (uint256 i = 0; i < _pids.length; i++) { 169 | uint256 pid = _pids[i]; 170 | uint256 amount = _votes[i]; 171 | require(tokenByPid[pid] != address(0), "Not approved for incentives"); 172 | pidVotes[pid][week] += amount; 173 | totalVotes[week] += amount; 174 | userPidVotes[msg.sender][pid][week] += amount; 175 | usedVotes += amount; 176 | } 177 | 178 | userVotes[msg.sender][week] = usedVotes; 179 | 180 | emit VotedForIncentives(msg.sender, _pids, _votes, usedVotes, type(uint256).max); 181 | } 182 | 183 | /** 184 | @dev Calculate and return the rewards per second for a given LP token. 185 | Called by `EllipsisLpStaker` when determining the emissions that each 186 | pool is entitled to. 187 | */ 188 | function getRewardsPerSecond(uint256 _pid, uint256 _week) 189 | external 190 | view 191 | virtual 192 | returns (uint256) 193 | { 194 | if (_week == 0) return 0; 195 | // weekly rewards are calculated based on the previous week's votes 196 | _week -= 1; 197 | 198 | uint256 votes = pidVotes[_pid][_week]; 199 | if (votes == 0) return 0; 200 | 201 | return (totalRewards[_week] * votes) / (totalVotes[_week] * WEEK); 202 | } 203 | 204 | /** 205 | @notice Modify the approval for a token to receive incentives. 206 | @dev This can only be called on tokens that were already voted in, it cannot 207 | be used to bypass the voting process. It is intended to block emissions in 208 | case of an exploit or act of maliciousness from a token within an approved pool. 209 | */ 210 | function addTokenApproval( 211 | address _token, 212 | address _strategy, 213 | bool _withUpdate 214 | ) external virtual onlyOwner returns (uint256) { 215 | uint256 pid = approvedPids.length; 216 | tokenByPid[pid] = _token; 217 | approvedPids.push(pid); 218 | return farming.addPool(_token, _strategy, _withUpdate); 219 | } 220 | 221 | function addReward(uint256 week, uint256 amount) external virtual { 222 | uint256 currentWeek = getWeek(); 223 | require(currentWeek <= week, "You can add rewards starting from the current week"); 224 | rewardToken.transferFrom(msg.sender, address(this), amount); 225 | uint256 totalAmount = totalRewards[week] + amount; 226 | totalRewards[week] = totalAmount; 227 | emit RewardChanged(msg.sender, week, int256(amount), totalAmount); 228 | } 229 | 230 | function removeReward(uint256 week, uint256 amount) external virtual onlyOwner { 231 | uint256 currentWeek = getWeek(); 232 | require(currentWeek < week, "You can remove rewards starting from the next week"); 233 | uint256 totalAmount = totalRewards[week] - amount; 234 | totalRewards[week] = totalAmount; 235 | rewardToken.transfer(msg.sender, amount); 236 | emit RewardChanged(msg.sender, week, -int256(amount), totalAmount); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /contracts/StableCoinStrategyCurve.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; 6 | import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; 7 | import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; 8 | import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 9 | 10 | import { IStrategy } from "./interfaces/IStrategy.sol"; 11 | import { IPancakeSwapFarm } from "./interfaces/IPancakeSwapFarm.sol"; 12 | import { IPancakeRouter02 } from "./interfaces/IPancakeRouter02.sol"; 13 | 14 | uint256 constant N_COINS = 2; 15 | 16 | // solhint-disable func-name-mixedcase 17 | // solhint-disable var-name-mixedcase 18 | interface IStableSwap { 19 | function get_dy( 20 | uint256 i, 21 | uint256 j, 22 | uint256 dx 23 | ) external view returns (uint256); 24 | 25 | function exchange( 26 | uint256 i, 27 | uint256 j, 28 | uint256 dx, 29 | uint256 min_dy 30 | ) external; 31 | 32 | function add_liquidity(uint256[N_COINS] memory amounts, uint256 min_mint_amount) external; 33 | } 34 | 35 | // solhint-disable max-states-count 36 | contract StableCoinStrategyCurve is 37 | IStrategy, 38 | OwnableUpgradeable, 39 | ReentrancyGuardUpgradeable, 40 | PausableUpgradeable 41 | { 42 | using SafeERC20Upgradeable for IERC20Upgradeable; 43 | 44 | event AutoharvestChanged(bool value); 45 | event MinEarnAmountChanged(uint256 indexed oldAmount, uint256 indexed newAmount); 46 | 47 | uint256 public pid; 48 | address public farmContractAddress; 49 | address public want; 50 | address public cake; 51 | address public token0; 52 | address public token1; 53 | address public router; 54 | address public stableSwap; 55 | address public helioFarming; 56 | 57 | address[] public earnedToStablePath; 58 | 59 | uint256 public i0; 60 | uint256 public i1; 61 | 62 | bool public enableAutoHarvest; 63 | 64 | uint256 public wantLockedTotal; 65 | uint256 public sharesTotal; 66 | 67 | uint256 public minEarnAmount; 68 | uint256 public constant MIN_EARN_AMOUNT_LL = 10**10; 69 | 70 | modifier onlyHelioFarming() { 71 | require(msg.sender == helioFarming, "!helio Farming"); 72 | _; 73 | } 74 | 75 | // 0 address _farmContractAddress, 76 | // 1 address _want, 77 | // 2 address _cake, 78 | // 3 address _token0, 79 | // 4 address _token1, 80 | // 5 address _router, 81 | // 6 address _stableSwap, 82 | // 7 address _helioFarming, 83 | function initialize( 84 | uint256 _pid, 85 | uint256 _i0, 86 | uint256 _i1, 87 | uint256 _minEarnAmount, 88 | bool _enableAutoHarvest, 89 | address[] memory _addresses, 90 | address[] memory _earnedToStablePath 91 | ) public initializer { 92 | __Ownable_init(); 93 | __ReentrancyGuard_init(); 94 | __Pausable_init(); 95 | require(_minEarnAmount >= MIN_EARN_AMOUNT_LL, "min earn amount is too low"); 96 | pid = _pid; 97 | i0 = _i0; 98 | i1 = _i1; 99 | minEarnAmount = _minEarnAmount; 100 | farmContractAddress = _addresses[0]; 101 | want = _addresses[1]; 102 | cake = _addresses[2]; 103 | token0 = _addresses[3]; 104 | token1 = _addresses[4]; 105 | router = _addresses[5]; 106 | stableSwap = _addresses[6]; 107 | helioFarming = _addresses[7]; 108 | enableAutoHarvest = _enableAutoHarvest; 109 | earnedToStablePath = _earnedToStablePath; 110 | } 111 | 112 | // Receives new deposits from user 113 | function deposit(address, uint256 _wantAmt) 114 | public 115 | virtual 116 | override 117 | onlyHelioFarming 118 | whenNotPaused 119 | returns (uint256) 120 | { 121 | if (enableAutoHarvest) { 122 | _harvest(); 123 | } 124 | IERC20Upgradeable(want).safeTransferFrom(address(msg.sender), address(this), _wantAmt); 125 | 126 | uint256 sharesAdded = _wantAmt; 127 | 128 | uint256 sharesTotalLocal = sharesTotal; 129 | uint256 wantLockedTotalLocal = wantLockedTotal; 130 | 131 | if (wantLockedTotalLocal > 0 && sharesTotalLocal > 0) { 132 | sharesAdded = (_wantAmt * sharesTotalLocal) / wantLockedTotalLocal; 133 | } 134 | sharesTotal = sharesTotalLocal + sharesAdded; 135 | 136 | _farm(); 137 | 138 | return sharesAdded; 139 | } 140 | 141 | function withdraw(address, uint256 _wantAmt) 142 | public 143 | virtual 144 | override 145 | onlyHelioFarming 146 | nonReentrant 147 | returns (uint256) 148 | { 149 | require(_wantAmt > 0, "_wantAmt <= 0"); 150 | 151 | if (enableAutoHarvest) { 152 | _harvest(); 153 | } 154 | 155 | uint256 sharesRemoved = (_wantAmt * sharesTotal) / wantLockedTotal; 156 | 157 | uint256 sharesTotalLocal = sharesTotal; 158 | if (sharesRemoved > sharesTotalLocal) { 159 | sharesRemoved = sharesTotalLocal; 160 | } 161 | sharesTotal = sharesTotalLocal - sharesRemoved; 162 | 163 | _unfarm(_wantAmt); 164 | 165 | uint256 wantAmt = IERC20Upgradeable(want).balanceOf(address(this)); 166 | if (_wantAmt > wantAmt) { 167 | _wantAmt = wantAmt; 168 | } 169 | 170 | if (wantLockedTotal < _wantAmt) { 171 | _wantAmt = wantLockedTotal; 172 | } 173 | 174 | wantLockedTotal -= _wantAmt; 175 | 176 | IERC20Upgradeable(want).safeTransfer(helioFarming, _wantAmt); 177 | 178 | return sharesRemoved; 179 | } 180 | 181 | function inCaseTokensGetStuck( 182 | address _token, 183 | uint256 _amount, 184 | address _to 185 | ) public virtual onlyOwner { 186 | require(_token != cake, "!safe"); 187 | require(_token != want, "!safe"); 188 | IERC20Upgradeable(_token).safeTransfer(_to, _amount); 189 | } 190 | 191 | function pause() public virtual onlyOwner { 192 | _pause(); 193 | } 194 | 195 | function unpause() public virtual onlyOwner { 196 | _unpause(); 197 | } 198 | 199 | function farm() public virtual nonReentrant { 200 | _farm(); 201 | } 202 | 203 | function _farm() internal virtual { 204 | uint256 wantAmt = IERC20Upgradeable(want).balanceOf(address(this)); 205 | wantLockedTotal += wantAmt; 206 | IERC20Upgradeable(want).safeIncreaseAllowance(farmContractAddress, wantAmt); 207 | 208 | IPancakeSwapFarm(farmContractAddress).deposit(pid, wantAmt); 209 | } 210 | 211 | function _unfarm(uint256 _wantAmt) internal virtual { 212 | IPancakeSwapFarm(farmContractAddress).withdraw(pid, _wantAmt); 213 | } 214 | 215 | // 1. Harvest farm tokens 216 | // 2. Converts farm tokens into want tokens 217 | // 3. Deposits want tokens 218 | function harvest() public virtual nonReentrant whenNotPaused { 219 | _harvest(); 220 | } 221 | 222 | // 1. Harvest farm tokens 223 | // 2. Converts farm tokens into want tokens 224 | // 3. Deposits want tokens 225 | function _harvest() internal virtual { 226 | // Harvest farm tokens 227 | _unfarm(0); 228 | 229 | // Converts farm tokens into want tokens 230 | uint256 earnedAmt = IERC20Upgradeable(cake).balanceOf(address(this)); 231 | 232 | IERC20Upgradeable(cake).safeApprove(router, 0); 233 | IERC20Upgradeable(cake).safeIncreaseAllowance(router, earnedAmt); 234 | 235 | if (earnedAmt < minEarnAmount) { 236 | return; 237 | } 238 | 239 | _safeSwapUni(router, earnedAmt, earnedToStablePath, address(this), block.timestamp + 700); 240 | 241 | uint256 token1Amt = IERC20Upgradeable(token1).balanceOf(address(this)); 242 | IERC20Upgradeable(token1).safeApprove(stableSwap, 0); 243 | IERC20Upgradeable(token1).safeIncreaseAllowance(stableSwap, token1Amt); 244 | 245 | _safeSwapCurve(stableSwap, i1, i0, token1Amt / 2); 246 | 247 | // Get want tokens, ie. add liquidity 248 | uint256 token0Amt = IERC20Upgradeable(token0).balanceOf(address(this)); 249 | token1Amt = IERC20Upgradeable(token1).balanceOf(address(this)); 250 | if (token0Amt > 0 && token1Amt > 0) { 251 | uint256[N_COINS] memory amounts; 252 | amounts[0] = token0Amt; 253 | amounts[1] = token1Amt; 254 | IERC20Upgradeable(token0).safeIncreaseAllowance(stableSwap, token0Amt); 255 | IERC20Upgradeable(token1).safeIncreaseAllowance(stableSwap, token1Amt); 256 | IStableSwap(stableSwap).add_liquidity(amounts, 0); 257 | } 258 | 259 | _farm(); 260 | } 261 | 262 | function _safeSwapCurve( 263 | address _stableSwap, 264 | uint256 _i, 265 | uint256 _j, 266 | uint256 _dx 267 | ) internal virtual { 268 | IStableSwap(_stableSwap).exchange(_i, _j, _dx, 0); 269 | } 270 | 271 | function _safeSwapUni( 272 | address _uniRouterAddress, 273 | uint256 _amountIn, 274 | address[] memory _path, 275 | address _to, 276 | uint256 _deadline 277 | ) internal virtual { 278 | IPancakeRouter02(_uniRouterAddress).swapExactTokensForTokens( 279 | _amountIn, 280 | 0, 281 | _path, 282 | _to, 283 | _deadline 284 | ); 285 | } 286 | 287 | function setAutoHarvest(bool _value) external onlyOwner { 288 | enableAutoHarvest = _value; 289 | emit AutoharvestChanged(_value); 290 | } 291 | 292 | function setMinEarnAmount(uint256 _minEarnAmount) external onlyOwner { 293 | require(_minEarnAmount >= MIN_EARN_AMOUNT_LL, "min earn amount is too low"); 294 | emit MinEarnAmountChanged(minEarnAmount, _minEarnAmount); 295 | minEarnAmount = _minEarnAmount; 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /contracts/TokenBonding.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 5 | import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 6 | import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; 7 | import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; 8 | import { IERC20Upgradeable, IERC20MetadataUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; 9 | 10 | import { ITokenBonding } from "./interfaces/ITokenBonding.sol"; 11 | 12 | contract TokenBonding is 13 | IERC20MetadataUpgradeable, 14 | ITokenBonding, 15 | Initializable, 16 | OwnableUpgradeable, 17 | ReentrancyGuardUpgradeable 18 | { 19 | using SafeERC20Upgradeable for IERC20Upgradeable; 20 | 21 | event TokenAdded(address indexed token, uint256 coefficient); 22 | event UnbondingRequest(address indexed token, uint256 amount, uint256 timestamp); 23 | 24 | string internal constant NAME = "vHELIO"; 25 | string internal constant SYMBOL = "vHELIO"; 26 | uint8 internal constant DECIMALS = 18; 27 | uint256 internal constant WEEK = 1 weeks; 28 | // uint256 internal constant WEEK = 20 minutes; 29 | uint256 internal _totalSupply; 30 | uint256 internal _totalWeight; 31 | 32 | struct TokenInfo { 33 | uint240 coefficient; 34 | uint16 index; 35 | uint256 totalStaked; 36 | } 37 | 38 | struct StakeInfo { 39 | uint256 staked; 40 | uint256 wantClaim; 41 | uint256 requestTime; 42 | } 43 | 44 | mapping(address => mapping(address => StakeInfo)) internal _userStakeInfo; 45 | mapping(address => TokenInfo) internal _tokenInfo; 46 | address[] internal _tokens; 47 | 48 | function initialize( 49 | address[] memory tokens_, 50 | uint240[] memory coefficients_ 51 | ) public initializer { 52 | __Ownable_init(); 53 | __ReentrancyGuard_init(); 54 | uint256 length = tokens_.length; 55 | require(length == coefficients_.length, "Not equal lengths"); 56 | for (uint16 i; i < length; ++i) { 57 | _tokenInfo[tokens_[i]] = TokenInfo({ 58 | coefficient: coefficients_[i], 59 | index: i + 1, 60 | totalStaked: 0 61 | }); 62 | _tokens.push(tokens_[i]); 63 | emit TokenAdded(tokens_[i], coefficients_[i]); 64 | } 65 | } 66 | 67 | function name() external pure returns (string memory) { 68 | return NAME; 69 | } 70 | 71 | function symbol() external pure returns (string memory) { 72 | return SYMBOL; 73 | } 74 | 75 | function decimals() external pure returns (uint8) { 76 | return DECIMALS; 77 | } 78 | 79 | function totalSupply() external view returns (uint256) { 80 | return _totalSupply; 81 | } 82 | 83 | function balanceOf(address user_) external view virtual returns (uint256) { 84 | uint256 userBalance_; 85 | unchecked { 86 | for (uint256 i; i < _tokens.length; ++i) { 87 | userBalance_ += 88 | ((uint256(_userStakeInfo[user_][_tokens[i]].staked) + 89 | _userStakeInfo[user_][_tokens[i]].wantClaim) * _tokenInfo[_tokens[i]].coefficient) / 90 | 10**18; 91 | } 92 | } 93 | return userBalance_; 94 | } 95 | 96 | function userWeight(address user_) external view virtual returns (uint256) { 97 | uint256 userWeight_; 98 | for (uint256 i; i < _tokens.length; ++i) { 99 | userWeight_ += 100 | (uint256(_userStakeInfo[user_][_tokens[i]].staked) * _tokenInfo[_tokens[i]].coefficient) / 101 | 10**18; 102 | } 103 | return userWeight_; 104 | } 105 | 106 | function totalWeight() external view returns (uint256) { 107 | return _totalWeight; 108 | } 109 | 110 | function allowance(address, address) external pure virtual returns (uint256) { 111 | return 0; 112 | } 113 | 114 | function transfer(address, uint256) external virtual returns (bool) { 115 | _nonTransferable(); 116 | } 117 | 118 | function transferFrom( 119 | address, 120 | address, 121 | uint256 122 | ) external virtual returns (bool) { 123 | _nonTransferable(); 124 | } 125 | 126 | function approve(address, uint256) external virtual returns (bool) { 127 | _nonTransferable(); 128 | } 129 | 130 | function _nonTransferable() internal virtual { 131 | revert("NON-TRANSFERABLE TOKEN"); 132 | } 133 | 134 | function bond(address token_, uint256 amount_) external virtual { 135 | _bond(token_, amount_, msg.sender); 136 | } 137 | 138 | function bondBatch(address[] memory tokens_, uint256[] memory amounts_) 139 | external 140 | virtual 141 | { 142 | uint256 length_ = tokens_.length; 143 | address user_ = msg.sender; 144 | require(length_ == amounts_.length, "tokens length must be equal to amounts length"); 145 | unchecked { 146 | for (uint256 i; i < length_; ++i) { 147 | _bond(tokens_[i], amounts_[i], user_); 148 | } 149 | } 150 | } 151 | 152 | function _bond( 153 | address token_, 154 | uint256 amount_, 155 | address user_ 156 | ) internal virtual nonReentrant { 157 | require(amount_ > 0, "cannot bond zero amount"); 158 | IERC20Upgradeable(token_).safeTransferFrom(user_, address(this), amount_); 159 | TokenInfo storage tokenInfo_ = _tokenInfo[token_]; 160 | require(tokenInfo_.index != 0, "Unsupported Token"); 161 | StakeInfo storage userStakeInfo_ = _userStakeInfo[user_][token_]; 162 | uint256 veTokenAmount = (uint256(amount_) * tokenInfo_.coefficient) / 10**18; 163 | tokenInfo_.totalStaked += amount_; 164 | userStakeInfo_.staked += amount_; 165 | _totalSupply += veTokenAmount; 166 | _totalWeight += veTokenAmount; 167 | emit Transfer(address(0), user_, veTokenAmount); 168 | } 169 | 170 | function requestUnbond(address token_, uint256 amount_) external virtual { 171 | _requestUnbond(token_, amount_, msg.sender); 172 | } 173 | 174 | function requestUnbondBatch(address[] memory tokens_, uint256[] memory amounts_) 175 | external 176 | virtual 177 | { 178 | uint256 length_ = tokens_.length; 179 | address user_ = msg.sender; 180 | require(length_ == amounts_.length, "tokens length must be equal to amounts length"); 181 | unchecked { 182 | for (uint256 i; i < length_; ++i) { 183 | _requestUnbond(tokens_[i], amounts_[i], user_); 184 | } 185 | } 186 | } 187 | 188 | function _requestUnbond( 189 | address token_, 190 | uint256 amount_, 191 | address user_ 192 | ) internal virtual { 193 | StakeInfo storage userStakeInfo_ = _userStakeInfo[user_][token_]; 194 | uint256 timestamp_ = uint256(block.timestamp); 195 | if (amount_ == type(uint256).max) { 196 | amount_ = userStakeInfo_.staked; 197 | } 198 | require(amount_ > 0, "Cannot request for zero amount"); 199 | userStakeInfo_.staked -= amount_; 200 | userStakeInfo_.wantClaim += amount_; 201 | userStakeInfo_.requestTime = timestamp_; 202 | _totalWeight -= (uint256(amount_) * _tokenInfo[token_].coefficient) / 10**18; 203 | emit UnbondingRequest(token_, amount_, timestamp_); 204 | } 205 | 206 | function unbond(address token_) external virtual { 207 | _unbond(token_, msg.sender); 208 | } 209 | 210 | function unbondBatch(address[] memory tokens_) external virtual { 211 | uint256 length_ = tokens_.length; 212 | address user_ = msg.sender; 213 | unchecked { 214 | for (uint256 i; i < length_; ++i) { 215 | _unbond(tokens_[i], user_); 216 | } 217 | } 218 | } 219 | 220 | function _unbond(address token_, address user_) internal virtual nonReentrant { 221 | StakeInfo storage userStakeInfo_ = _userStakeInfo[user_][token_]; 222 | TokenInfo storage tokenInfo_ = _tokenInfo[token_]; 223 | uint256 timestamp_ = uint256(block.timestamp); 224 | uint256 amount_ = userStakeInfo_.wantClaim; 225 | require(amount_ > 0, "Claimed amount should not be zero"); 226 | require(userStakeInfo_.requestTime + WEEK <= timestamp_, "You should wait seven days"); 227 | uint256 veTokenAmount = (uint256(amount_) * tokenInfo_.coefficient) / 10**18; 228 | tokenInfo_.totalStaked -= amount_; 229 | userStakeInfo_.wantClaim = 0; 230 | _totalSupply -= veTokenAmount; 231 | IERC20Upgradeable(token_).safeTransfer(user_, amount_); 232 | emit Transfer(user_, address(0), veTokenAmount); 233 | } 234 | 235 | function decreaseUnbondAmount(address token_, uint256 amount_) external virtual { 236 | _decreaseUnbondAmount(token_, amount_, msg.sender); 237 | } 238 | 239 | function decreaseUnbondAmountBatch(address[] memory tokens_, uint256[] memory amounts_) 240 | external 241 | virtual 242 | { 243 | uint256 length_ = tokens_.length; 244 | address user_ = msg.sender; 245 | require(length_ == amounts_.length, "tokens length must be equal to amounts length"); 246 | unchecked { 247 | for (uint256 i; i < length_; ++i) { 248 | _decreaseUnbondAmount(tokens_[i], amounts_[i], user_); 249 | } 250 | } 251 | } 252 | 253 | function _decreaseUnbondAmount( 254 | address token_, 255 | uint256 amount_, 256 | address user_ 257 | ) internal virtual { 258 | StakeInfo storage userStakeInfo_ = _userStakeInfo[user_][token_]; 259 | uint256 wantClaim_ = userStakeInfo_.wantClaim; 260 | if (amount_ == type(uint256).max) { 261 | amount_ = wantClaim_; 262 | } 263 | require(amount_ <= wantClaim_, "amount is more than wantClaim amount"); 264 | userStakeInfo_.staked += amount_; 265 | _totalWeight += (uint256(amount_) * _tokenInfo[token_].coefficient) / 10**18; 266 | unchecked { 267 | uint256 remainingAmount = wantClaim_ - amount_; 268 | userStakeInfo_.wantClaim = remainingAmount; 269 | emit UnbondingRequest(token_, remainingAmount, userStakeInfo_.requestTime); 270 | } 271 | } 272 | 273 | // 2 * 10 ** 18 274 | function addToken(address newToken_, uint240 coefficient_) external virtual onlyOwner { 275 | require(_tokenInfo[newToken_].index == 0, "Token already added"); 276 | _tokens.push(newToken_); 277 | _tokenInfo[newToken_] = TokenInfo({ 278 | coefficient: coefficient_, 279 | index: uint16(_tokens.length), 280 | totalStaked: 0 281 | }); 282 | 283 | emit TokenAdded(newToken_, coefficient_); 284 | } 285 | 286 | function tokenInfo(address token_) public view virtual returns (TokenInfo memory) { 287 | TokenInfo memory tokenInfo_ = _tokenInfo[token_]; 288 | require(tokenInfo_.index != 0, "Unsupported token"); 289 | return tokenInfo_; 290 | } 291 | 292 | function userStakeInfo(address user_, address token_) external view returns (StakeInfo memory) { 293 | return _userStakeInfo[user_][token_]; 294 | } 295 | 296 | function getTokensLength() external view returns (uint256) { 297 | return _tokens.length; 298 | } 299 | 300 | function getTokenByIndex(uint256 index) external view virtual returns (address) { 301 | return _tokens[index - 1]; 302 | } 303 | 304 | function getTokenInfoByIndex(uint256 index) external view virtual returns (TokenInfo memory) { 305 | return tokenInfo(_tokens[index - 1]); 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /contracts/upgrades/IncentiveVotingTemp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { ITokenBonding } from "../interfaces/ITokenBonding.sol"; 5 | 6 | import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 7 | import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 8 | import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 9 | 10 | import { IFarming } from "../interfaces/IFarming.sol"; 11 | import { ITokenBonding } from "../interfaces/ITokenBonding.sol"; 12 | import { IExternalPool } from "../interfaces/IExternalPool.sol"; 13 | import { IIncentiveVoting } from "../interfaces/IIncentiveVoting.sol"; 14 | 15 | struct Vote { 16 | address addr; 17 | bool isExternal; 18 | uint256 pid; 19 | uint256 votes; 20 | } 21 | 22 | struct Pool { 23 | uint256 id; 24 | bool isExternal; 25 | } 26 | 27 | // solhint-disable-next-line max-states-count 28 | contract IncentiveVotingTemp is IIncentiveVoting, Initializable, OwnableUpgradeable { 29 | // pid -> week -> votes received 30 | mapping(uint256 => uint256[65535]) public pidVotes; 31 | 32 | // user -> week -> votes used 33 | mapping(address => uint256[65535]) public userVotes; 34 | 35 | // user -> pid -> week -> votes for pool 36 | mapping(address => mapping(uint256 => uint256[65535])) public userPidVotes; 37 | 38 | // week -> total votes used 39 | uint256[65535] public totalPidVotes; 40 | 41 | // week -> addedRewards 42 | uint256[65535] public totalPidRewards; 43 | 44 | uint256 internal constant WEEK = 1 weeks; 45 | // uint256 internal constant WEEK = 20 minutes; 46 | uint256 public override startTime; 47 | 48 | ITokenBonding public tokenBonding; 49 | IFarming public farming; 50 | 51 | mapping(uint256 => address) public tokenByPid; 52 | uint256[] public approvedPids; 53 | 54 | IERC20Upgradeable public rewardToken; 55 | 56 | // externalPid -> week -> votes received 57 | mapping(uint256 => uint256[65535]) public externalPidVotes; 58 | // user -> externalPid -> week -> votes for pool 59 | mapping(address => mapping(uint256 => uint256[65535])) public userExternalPidVotes; 60 | 61 | // week -> total votes used 62 | uint256[65535] public totalExternalPidVotes; 63 | 64 | mapping(uint256 => address) public externalPoolById; 65 | uint256[] public approvedExternalPids; 66 | 67 | bool[65535] public externalDistributionDone; 68 | 69 | event VotedForIncentives( 70 | address indexed voter, 71 | Pool[] pids, 72 | uint256[] votes, 73 | uint256 userVotesUsed, 74 | uint256 totalUserVotes 75 | ); 76 | 77 | event RewardChanged( 78 | address indexed user, 79 | uint256 indexed week, 80 | int256 amount, 81 | uint256 totalAmount 82 | ); 83 | 84 | function initialize(uint256 startTime_) public initializer { 85 | require(startTime_ > block.timestamp, "!epoch week"); 86 | __Ownable_init(); 87 | startTime = startTime_; 88 | } 89 | 90 | function setFarming( 91 | IFarming _farming, 92 | address[] calldata _initialApprovedTokens, 93 | address[] calldata _strategies 94 | ) external virtual returns (uint256[] memory) { 95 | require(address(farming) == address(0), "farming addresExternals can be set only once"); 96 | uint256 length = _initialApprovedTokens.length; 97 | require(length == _strategies.length, "lengths are not equal"); 98 | uint256[] memory _pids = new uint256[](length); 99 | farming = _farming; 100 | rewardToken = _farming.rewardToken(); 101 | rewardToken.approve(address(_farming), type(uint256).max); 102 | for (uint256 i = 0; i < _initialApprovedTokens.length; i++) { 103 | address token = _initialApprovedTokens[i]; 104 | tokenByPid[i] = token; 105 | approvedPids.push(i); 106 | _pids[i] = _farming.addPool(token, _strategies[i], false); 107 | } 108 | return _pids; 109 | } 110 | 111 | function approvedPoolsLength() external view returns (uint256) { 112 | return approvedPids.length; 113 | } 114 | 115 | function getWeek() public view virtual returns (uint256) { 116 | if (startTime > block.timestamp) return 0; 117 | return (block.timestamp - startTime) / WEEK; 118 | } 119 | 120 | /** 121 | @notice Get data on the current votes made in the active week 122 | @return _totalPidVotes Total number of votes this week for all internal pools 123 | @return _totalExternalPidVotes Total number of votes this week for all esternal pools 124 | @return _voteData Dynamic array of (token address, votes for token) 125 | */ 126 | function getVotes(uint256 _week) 127 | external 128 | view 129 | virtual 130 | returns ( 131 | uint256 _totalPidVotes, 132 | uint256 _totalExternalPidVotes, 133 | Vote[] memory _voteData 134 | ) 135 | { 136 | uint256 totalLength = approvedPids.length + approvedExternalPids.length; 137 | _voteData = new Vote[](totalLength); 138 | for (uint256 i = 0; i < approvedPids.length; i++) { 139 | address token = tokenByPid[i]; 140 | _voteData[i] = Vote({ addr: token, votes: pidVotes[i][_week], pid: i, isExternal: false }); 141 | } 142 | for (uint256 i = approvedPids.length; i < totalLength; i++) { 143 | address farm = externalPoolById[i]; 144 | _voteData[i] = Vote({ 145 | addr: farm, 146 | votes: externalPidVotes[i][_week], 147 | pid: i, 148 | isExternal: true 149 | }); 150 | } 151 | return (totalPidVotes[_week], _totalExternalPidVotes, _voteData); 152 | } 153 | 154 | /** 155 | @notice Get data on current votes `_user` has made in the active week 156 | @return _totalPidVotes Total number of votes from `_user` this week for all pools 157 | @return _voteData Dynamic array of (token address, votes for token) 158 | */ 159 | function getUserVotes(address _user, uint256 _week) 160 | external 161 | view 162 | virtual 163 | returns (uint256 _totalPidVotes, Vote[] memory _voteData) 164 | { 165 | uint256 totalLength = approvedPids.length + approvedExternalPids.length; 166 | _voteData = new Vote[](totalLength); 167 | for (uint256 i = 0; i < approvedPids.length; i++) { 168 | address token = tokenByPid[i]; 169 | _voteData[i] = Vote({ 170 | addr: token, 171 | votes: userPidVotes[_user][i][_week], 172 | pid: i, 173 | isExternal: false 174 | }); 175 | } 176 | for (uint256 i = approvedExternalPids.length; i < totalLength; i++) { 177 | address farm = externalPoolById[i]; 178 | _voteData[i] = Vote({ 179 | addr: farm, 180 | votes: userExternalPidVotes[_user][i][_week], 181 | pid: i, 182 | isExternal: true 183 | }); 184 | } 185 | return (userVotes[_user][_week], _voteData); 186 | } 187 | 188 | /** 189 | @notice Get the amount of unused votes for for the current week being voted on 190 | @param _user Address to query 191 | @return uint Amount of unused votes 192 | */ 193 | function availableVotes(address _user) external view virtual returns (uint256) { 194 | uint256 week = getWeek(); 195 | uint256 usedVotes = userVotes[_user][week]; 196 | uint256 _totalPidVotes = tokenBonding.userWeight(_user) / 1e18; 197 | return _totalPidVotes - usedVotes; 198 | } 199 | 200 | /** 201 | @notice Allocate votes toward LP tokens to receive emissions in the following week 202 | @dev A user may vote as many times as they like within a week, so long as their total 203 | available votes are not exceeded. If they receive additional votes by locking more 204 | tokens within `tokenBonding`, they can vote immediately. 205 | 206 | Votes can only be added - not modified or removed. Votes only apply to the 207 | following week - they do not carry over. A user must resubmit their vote each 208 | week. 209 | @param _pids List of pool ids of LP tokens to vote for 210 | @param _votes Votes to allocate to `_tokens`. Values are additive, they do 211 | not include previous votes. For example, if you have already 212 | allocated 100 votes and wish to allocate a total of 300, 213 | the vote amount should be given as 200. 214 | */ 215 | // pool id 0 - vote - 30 216 | // pool id 1 - vote - 60 217 | function vote(Pool[] calldata _pids, uint256[] calldata _votes) external virtual { 218 | require(_pids.length == _votes.length, "Input length mismatch"); 219 | 220 | // update rewards per second, if required 221 | uint256 week = getWeek(); 222 | 223 | // update accounting for this week's votes 224 | uint256 usedVotes = userVotes[msg.sender][week]; 225 | for (uint256 i = 0; i < _pids.length; i++) { 226 | Pool memory pid = _pids[i]; 227 | uint256 amount = _votes[i]; 228 | if (pid.isExternal) { 229 | require(externalPoolById[pid.id] != address(0), "Not approved for incentives"); 230 | externalPidVotes[pid.id][week] += amount; 231 | totalExternalPidVotes[week] += amount; 232 | userExternalPidVotes[msg.sender][pid.id][week] += amount; 233 | } else { 234 | require(tokenByPid[pid.id] != address(0), "Not approved for incentives"); 235 | pidVotes[pid.id][week] += amount; 236 | totalPidVotes[week] += amount; 237 | userPidVotes[msg.sender][pid.id][week] += amount; 238 | } 239 | usedVotes += amount; 240 | } 241 | 242 | // make sure user has not exceeded available votes 243 | uint256 _totalUserVotes = tokenBonding.userWeight(msg.sender) / 1e18; 244 | require(usedVotes <= _totalUserVotes, "Available votes exceeded"); 245 | userVotes[msg.sender][week] = usedVotes; 246 | 247 | emit VotedForIncentives(msg.sender, _pids, _votes, usedVotes, _totalUserVotes); 248 | } 249 | 250 | function setTokenBonding(address _tokenBonding) external { 251 | require(address(tokenBonding) == address(0), "already setted!"); 252 | tokenBonding = ITokenBonding(_tokenBonding); 253 | } 254 | 255 | /** 256 | @dev Calculate and return the rewards per second for a given LP token. 257 | Called by `EllipsisLpStaker` when determining the emissions that each 258 | pool is entitled to. 259 | */ 260 | function getRewardsPerSecond(uint256 _pid, uint256 _week) 261 | external 262 | view 263 | virtual 264 | returns (uint256) 265 | { 266 | if (_week == 0) return 0; 267 | // weekly rewards are calculated based on the previous week's votes 268 | _week -= 1; 269 | 270 | uint256 votes = pidVotes[_pid][_week]; 271 | if (votes == 0) return 0; 272 | uint256 currentWeekTotalPidVotes = totalPidVotes[_week]; 273 | uint256 rewards = (totalPidRewards[_week] * currentWeekTotalPidVotes) / 274 | (totalExternalPidVotes[_week] + currentWeekTotalPidVotes); 275 | 276 | return (rewards * votes) / (currentWeekTotalPidVotes * WEEK); 277 | } 278 | 279 | function distributeRewards(uint256 _week) external virtual returns (bool) { 280 | require(!externalDistributionDone[_week], "already distributed for this week"); 281 | if (_week == 0) return false; 282 | // weekly rewards are calculated based on the previous week's votes 283 | _week -= 1; 284 | 285 | uint256 currentWeekTotalExternalPidVotes = totalExternalPidVotes[_week]; 286 | uint256 rewards = (totalPidRewards[_week] * currentWeekTotalExternalPidVotes) / 287 | (currentWeekTotalExternalPidVotes + totalPidVotes[_week]); 288 | for (uint256 _pid; _pid < approvedExternalPids.length; ++_pid) { 289 | uint256 votes = externalPidVotes[_pid][_week]; 290 | if (votes == 0) return false; 291 | uint256 pidReward = (rewards * votes) / (currentWeekTotalExternalPidVotes * WEEK); 292 | address externalPool = externalPoolById[_pid]; 293 | if (rewardToken.allowance(address(this), externalPool) < pidReward) { 294 | rewardToken.approve(externalPool, type(uint256).max); 295 | } 296 | IExternalPool(externalPool).addReward(pidReward); 297 | } 298 | externalDistributionDone[_week] = true; 299 | return true; 300 | } 301 | 302 | function addTokenApproval( 303 | address _token, 304 | address _strategy, 305 | bool _withUpdate 306 | ) external virtual onlyOwner returns (uint256) { 307 | uint256 pid = approvedPids.length; 308 | tokenByPid[pid] = _token; 309 | approvedPids.push(pid); 310 | return farming.addPool(_token, _strategy, _withUpdate); 311 | } 312 | 313 | function addExternalPool(address _externalPool) external virtual onlyOwner returns (uint256) { 314 | uint256 pid = approvedExternalPids.length; 315 | externalPoolById[pid] = _externalPool; 316 | approvedExternalPids.push(pid); 317 | return pid; 318 | } 319 | 320 | function addReward(uint256 week, uint256 amount) public virtual { 321 | uint256 currentWeek = getWeek(); 322 | require(currentWeek <= week, "You can add rewards starting from the current week"); 323 | rewardToken.transferFrom(msg.sender, address(this), amount); 324 | uint256 totalAmount = totalPidRewards[week] + amount; 325 | totalPidRewards[week] = totalAmount; 326 | emit RewardChanged(msg.sender, week, int256(amount), totalAmount); 327 | } 328 | 329 | function removeReward(uint256 week, uint256 amount) external virtual onlyOwner { 330 | uint256 currentWeek = getWeek(); 331 | require(currentWeek < week, "You can remove rewards starting from the next week"); 332 | uint256 totalAmount = totalPidRewards[week] - amount; 333 | totalPidRewards[week] = totalAmount; 334 | rewardToken.transfer(msg.sender, amount); 335 | emit RewardChanged(msg.sender, week, -int256(amount), totalAmount); 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /contracts/Farming.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 5 | import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 6 | import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 7 | import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; 8 | import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; 9 | 10 | import { IFarming } from "./interfaces/IFarming.sol"; 11 | import { IStrategy } from "./interfaces/IStrategy.sol"; 12 | import { IIncentiveVoting } from "./interfaces/IIncentiveVoting.sol"; 13 | 14 | contract Farming is IFarming, Initializable, ReentrancyGuardUpgradeable, OwnableUpgradeable { 15 | using SafeERC20Upgradeable for IERC20Upgradeable; 16 | 17 | // Info of each user. 18 | struct UserInfo { 19 | uint256 shares; 20 | uint256 rewardDebt; 21 | uint256 claimable; 22 | } 23 | // Info of each pool. 24 | struct PoolInfo { 25 | IERC20Upgradeable token; 26 | IStrategy strategy; 27 | uint256 rewardsPerSecond; 28 | uint256 lastRewardTime; // Last second that reward distribution occurs. 29 | uint256 accRewardPerShare; // Accumulated rewards per share, times 1e12. See below. 30 | } 31 | 32 | // uint256 internal constant WEEK = 20 minutes; 33 | uint256 internal constant WEEK = 1 weeks; 34 | 35 | // Info of each pool. 36 | // address[] public registeredTokens; 37 | // mapping(address => PoolInfo) public poolInfo; 38 | PoolInfo[] public poolInfo; // Info of each pool. 39 | 40 | // pid => user => Info of each user that stakes LP tokens. 41 | mapping(uint256 => mapping(address => UserInfo)) public userInfo; 42 | // The timestamp when reward mining starts. 43 | uint256 public startTime; 44 | 45 | // account earning rewards => receiver of rewards for this account 46 | // if receiver is set to address(0), rewards are paid to the earner 47 | // this is used to aid 3rd party contract integrations 48 | mapping(address => address) public claimReceiver; 49 | 50 | // when set to true, other accounts cannot call 51 | // `claim` on behalf of an account 52 | mapping(address => bool) public blockThirdPartyActions; 53 | 54 | IERC20Upgradeable public rewardToken; 55 | IIncentiveVoting public incentiveVoting; 56 | 57 | event Deposit(address indexed caller, address indexed user, uint256 indexed pid, uint256 amount); 58 | event Withdraw(address indexed user, uint256 indexed pid, uint256 amount); 59 | event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount); 60 | event ClaimedReward( 61 | address indexed caller, 62 | address indexed claimer, 63 | address indexed receiver, 64 | uint256 amount 65 | ); 66 | 67 | function initialize(IERC20Upgradeable _rewardToken, IIncentiveVoting _incentiveVoting) 68 | public 69 | initializer 70 | { 71 | __ReentrancyGuard_init(); 72 | __Ownable_init(); 73 | startTime = _incentiveVoting.startTime(); 74 | rewardToken = _rewardToken; 75 | incentiveVoting = _incentiveVoting; 76 | } 77 | 78 | /** 79 | @notice The current number of stakeable LP tokens 80 | */ 81 | function poolLength() external view returns (uint256) { 82 | return poolInfo.length; 83 | } 84 | 85 | /** 86 | @notice Add a new token that may be staked within this contract 87 | @dev Called by `IncentiveVoting` after a successful token approval vote 88 | */ 89 | function addPool( 90 | address _token, 91 | address _strategy, 92 | bool _withUpdate 93 | ) external virtual returns (uint256) { 94 | require(msg.sender == address(incentiveVoting), "Sender not incentiveVoting"); 95 | if (_withUpdate) { 96 | massUpdatePools(); 97 | } 98 | poolInfo.push( 99 | PoolInfo({ 100 | token: IERC20Upgradeable(_token), 101 | strategy: IStrategy(_strategy), 102 | rewardsPerSecond: 0, 103 | lastRewardTime: block.timestamp, 104 | accRewardPerShare: 0 105 | }) 106 | ); 107 | return poolInfo.length - 1; 108 | } 109 | 110 | /** 111 | @notice Set the claim receiver address for the caller 112 | @dev When the claim receiver is not == address(0), all 113 | emission claims are transferred to this address 114 | @param _receiver Claim receiver address 115 | */ 116 | function setClaimReceiver(address _receiver) external virtual { 117 | claimReceiver[msg.sender] = _receiver; 118 | } 119 | 120 | function setBlockThirdPartyActions(bool _block) external virtual { 121 | blockThirdPartyActions[msg.sender] = _block; 122 | } 123 | 124 | // View function to see staked Want tokens on frontend. 125 | function stakedWantTokens(uint256 _pid, address _user) external view virtual returns (uint256) { 126 | PoolInfo storage pool = poolInfo[_pid]; 127 | UserInfo storage user = userInfo[_pid][_user]; 128 | 129 | uint256 sharesTotal = pool.strategy.sharesTotal(); 130 | uint256 wantLockedTotal = pool.strategy.wantLockedTotal(); 131 | if (sharesTotal == 0) { 132 | return 0; 133 | } 134 | return (user.shares * wantLockedTotal) / sharesTotal; 135 | } 136 | 137 | // Update reward variables for all pools. Be careful of gas spending! 138 | function massUpdatePools() public virtual { 139 | uint256 length = poolInfo.length; 140 | for (uint256 pid = 0; pid < length; ++pid) { 141 | updatePool(pid); 142 | } 143 | } 144 | 145 | // Update reward variables of the given pool to be up-to-date. 146 | function updatePool(uint256 _pid) public virtual returns (uint256 accRewardPerShare) { 147 | PoolInfo storage pool = poolInfo[_pid]; 148 | uint256 lastRewardTime = pool.lastRewardTime; 149 | require(lastRewardTime > 0, "Invalid pool"); 150 | if (block.timestamp <= lastRewardTime) { 151 | return pool.accRewardPerShare; 152 | } 153 | (accRewardPerShare, pool.rewardsPerSecond) = _getRewardData(_pid); 154 | pool.lastRewardTime = block.timestamp; 155 | if (accRewardPerShare == 0) return pool.accRewardPerShare; 156 | accRewardPerShare += pool.accRewardPerShare; 157 | pool.accRewardPerShare = accRewardPerShare; 158 | return accRewardPerShare; 159 | } 160 | 161 | /** 162 | @notice Get the current number of unclaimed rewards for a user on one or more tokens 163 | @param _user User to query pending rewards for 164 | @param _pids Array of token addresses to query 165 | @return uint256[] Unclaimed rewards 166 | */ 167 | function claimableReward(address _user, uint256[] calldata _pids) 168 | external 169 | view 170 | virtual 171 | returns (uint256[] memory) 172 | { 173 | uint256[] memory claimable = new uint256[](_pids.length); 174 | for (uint256 i = 0; i < _pids.length; i++) { 175 | uint256 pid = _pids[i]; 176 | PoolInfo storage pool = poolInfo[pid]; 177 | UserInfo storage user = userInfo[pid][_user]; 178 | (uint256 accRewardPerShare, ) = _getRewardData(pid); 179 | accRewardPerShare += pool.accRewardPerShare; 180 | claimable[i] = user.claimable + (user.shares * accRewardPerShare) / 1e12 - user.rewardDebt; 181 | } 182 | return claimable; 183 | } 184 | 185 | // Get updated reward data for the given token 186 | function _getRewardData(uint256 _pid) 187 | internal 188 | view 189 | virtual 190 | returns (uint256 accRewardPerShare, uint256 rewardsPerSecond) 191 | { 192 | PoolInfo storage pool = poolInfo[_pid]; 193 | uint256 lpSupply = pool.strategy.sharesTotal(); 194 | uint256 start = startTime; 195 | uint256 currentWeek = (block.timestamp - start) / WEEK; 196 | 197 | if (lpSupply == 0) { 198 | return (0, incentiveVoting.getRewardsPerSecond(_pid, currentWeek)); 199 | } 200 | 201 | uint256 lastRewardTime = pool.lastRewardTime; 202 | uint256 rewardWeek = (lastRewardTime - start) / WEEK; 203 | rewardsPerSecond = pool.rewardsPerSecond; 204 | uint256 reward; 205 | uint256 duration; 206 | while (rewardWeek < currentWeek) { 207 | rewardWeek++; 208 | uint256 nextRewardTime = rewardWeek * WEEK + start; 209 | duration = nextRewardTime - lastRewardTime; 210 | reward += duration * rewardsPerSecond; 211 | rewardsPerSecond = incentiveVoting.getRewardsPerSecond(_pid, rewardWeek); 212 | lastRewardTime = nextRewardTime; 213 | } 214 | 215 | duration = block.timestamp - lastRewardTime; 216 | reward += duration * rewardsPerSecond; 217 | return ((reward * 1e12) / lpSupply, rewardsPerSecond); 218 | } 219 | 220 | function deposit( 221 | uint256 _pid, 222 | uint256 _wantAmt, 223 | bool _claimRewards, 224 | address _userAddress 225 | ) external virtual nonReentrant returns (uint256) { 226 | require(_wantAmt > 0, "Cannot deposit zero"); 227 | if (msg.sender != _userAddress) { 228 | _claimRewards = false; 229 | } 230 | uint256 _accRewardPerShare = updatePool(_pid); 231 | PoolInfo storage pool = poolInfo[_pid]; 232 | UserInfo storage user = userInfo[_pid][_userAddress]; 233 | 234 | uint256 pending; 235 | if (user.shares > 0) { 236 | pending = (user.shares * _accRewardPerShare) / 1e12 - user.rewardDebt; 237 | if (_claimRewards) { 238 | pending += user.claimable; 239 | user.claimable = 0; 240 | pending = _safeRewardTransfer(_userAddress, pending); 241 | } else if (pending > 0) { 242 | user.claimable += pending; 243 | pending = 0; 244 | } 245 | } 246 | 247 | pool.token.safeTransferFrom(msg.sender, address(this), _wantAmt); 248 | pool.token.safeIncreaseAllowance(address(pool.strategy), _wantAmt); 249 | uint256 sharesAdded = pool.strategy.deposit(_userAddress, _wantAmt); 250 | user.shares += sharesAdded; 251 | 252 | user.rewardDebt = (user.shares * pool.accRewardPerShare) / 1e12; 253 | emit Deposit(msg.sender, _userAddress, _pid, _wantAmt); 254 | return pending; 255 | } 256 | 257 | /** 258 | @notice Withdraw LP tokens from the contract 259 | @dev Also updates the caller's current boost 260 | @param _pid LP token address to withdraw. 261 | @param _wantAmt Amount of tokens to withdraw. 262 | @param _claimRewards If true, also claim rewards earned on the token. 263 | @return uint256 Claimed reward amount 264 | */ 265 | function withdraw( 266 | uint256 _pid, 267 | uint256 _wantAmt, 268 | bool _claimRewards 269 | ) public virtual nonReentrant returns (uint256) { 270 | address _userAddress = msg.sender; 271 | require(_wantAmt > 0, "Cannot withdraw zero"); 272 | uint256 accRewardPerShare = updatePool(_pid); 273 | PoolInfo storage pool = poolInfo[_pid]; 274 | UserInfo storage user = userInfo[_pid][_userAddress]; 275 | 276 | uint256 sharesTotal = pool.strategy.sharesTotal(); 277 | 278 | require(user.shares > 0, "user.shares is 0"); 279 | require(sharesTotal > 0, "sharesTotal is 0"); 280 | 281 | uint256 pending = (user.shares * accRewardPerShare) / 1e12 - user.rewardDebt; 282 | if (_claimRewards) { 283 | pending += user.claimable; 284 | user.claimable = 0; 285 | pending = _safeRewardTransfer(_userAddress, pending); 286 | } else if (pending > 0) { 287 | user.claimable += pending; 288 | pending = 0; 289 | } 290 | // Withdraw want tokens 291 | uint256 amount = (user.shares * pool.strategy.wantLockedTotal()) / sharesTotal; 292 | if (_wantAmt > amount) { 293 | _wantAmt = amount; 294 | } 295 | uint256 sharesRemoved = pool.strategy.withdraw(_userAddress, _wantAmt); 296 | 297 | if (sharesRemoved > user.shares) { 298 | user.shares = 0; 299 | } else { 300 | user.shares -= sharesRemoved; 301 | } 302 | 303 | IERC20Upgradeable token = pool.token; 304 | uint256 wantBal = token.balanceOf(address(this)); 305 | if (wantBal < _wantAmt) { 306 | _wantAmt = wantBal; 307 | } 308 | user.rewardDebt = (user.shares * pool.accRewardPerShare) / 1e12; 309 | token.safeTransfer(_userAddress, _wantAmt); 310 | 311 | emit Withdraw(_userAddress, _pid, _wantAmt); 312 | return pending; 313 | } 314 | 315 | function withdrawAll(uint256 _pid, bool _claimRewards) external virtual returns (uint256) { 316 | return withdraw(_pid, type(uint256).max, _claimRewards); 317 | } 318 | 319 | /** 320 | @notice Claim pending rewards for one or more tokens for a user. 321 | @param _user Address to claim rewards for. Reverts if the caller is not the 322 | claimer and the claimer has blocked third-party actions. 323 | @param _pids Array of LP token addresses to claim for. 324 | @return uint256 Claimed reward amount 325 | */ 326 | function claim(address _user, uint256[] calldata _pids) external virtual returns (uint256) { 327 | if (msg.sender != _user) { 328 | require(!blockThirdPartyActions[_user], "Cannot claim on behalf of this account"); 329 | } 330 | 331 | // calculate claimable amount 332 | uint256 pending; 333 | for (uint256 i = 0; i < _pids.length; i++) { 334 | uint256 pid = _pids[i]; 335 | uint256 accRewardPerShare = updatePool(pid); 336 | UserInfo storage user = userInfo[pid][_user]; 337 | uint256 rewardDebt = (user.shares * accRewardPerShare) / 1e12; 338 | pending += user.claimable + rewardDebt - user.rewardDebt; 339 | user.claimable = 0; 340 | user.rewardDebt = (user.shares * poolInfo[pid].accRewardPerShare) / 1e12; 341 | } 342 | return _safeRewardTransfer(_user, pending); 343 | } 344 | 345 | // Safe reward token transfer function, just in case if rounding error causes pool to not have enough 346 | function _safeRewardTransfer(address _user, uint256 _rewardAmt) 347 | internal 348 | virtual 349 | returns (uint256) 350 | { 351 | uint256 rewardBal = rewardToken.balanceOf(address(incentiveVoting)); 352 | if (_rewardAmt > rewardBal) { 353 | _rewardAmt = rewardBal; 354 | } 355 | if (_rewardAmt > 0) { 356 | address receiver = claimReceiver[_user]; 357 | if (receiver == address(0)) { 358 | receiver = _user; 359 | } 360 | rewardToken.transferFrom(address(incentiveVoting), receiver, _rewardAmt); 361 | emit ClaimedReward(msg.sender, _user, receiver, _rewardAmt); 362 | } 363 | return _rewardAmt; 364 | } 365 | 366 | function inCaseTokensGetStuck(address _token, uint256 _amount) public virtual onlyOwner { 367 | IERC20Upgradeable(_token).safeTransfer(msg.sender, _amount); 368 | } 369 | 370 | // Withdraw without caring about rewards. EMERGENCY ONLY. 371 | function emergencyWithdraw(uint256 _pid) public virtual nonReentrant { 372 | address _userAddress = msg.sender; 373 | PoolInfo storage pool = poolInfo[_pid]; 374 | UserInfo storage user = userInfo[_pid][_userAddress]; 375 | 376 | uint256 wantLockedTotal = pool.strategy.wantLockedTotal(); 377 | uint256 sharesTotal = pool.strategy.sharesTotal(); 378 | uint256 amount = (user.shares * wantLockedTotal) / sharesTotal; 379 | 380 | pool.strategy.withdraw(_userAddress, amount); 381 | 382 | pool.token.safeTransfer(_userAddress, amount); 383 | emit EmergencyWithdraw(_userAddress, _pid, amount); 384 | delete userInfo[_pid][_userAddress]; 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /test/1_IncentiveVoting.test.ts: -------------------------------------------------------------------------------- 1 | import chai, { expect } from "chai"; 2 | import chaiAsPromised from "chai-as-promised"; 3 | import { ethers, upgrades } from "hardhat"; 4 | import { BigNumber } from "ethers"; 5 | import { solidity } from "ethereum-waffle"; 6 | 7 | import { 8 | advanceTime, 9 | daysToSeconds, 10 | getNextTimestampDivisibleBy, 11 | setTimestamp, 12 | } from "../helpers/utils"; 13 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 14 | import { FakeERC20, Farming, IncentiveVoting } from "../typechain-types"; 15 | import NetworkSnapshotter from "../helpers/NetworkSnapshotter"; 16 | 17 | const { AddressZero, MaxUint256 } = ethers.constants; 18 | 19 | chai.use(solidity); 20 | chai.use(chaiAsPromised); 21 | 22 | const week = daysToSeconds(BigNumber.from(7)); 23 | const ten = BigNumber.from(10); 24 | const tenPow18 = ten.pow(18); 25 | 26 | describe("IncentiveVoting", () => { 27 | let deployer: SignerWithAddress; 28 | let signer1: SignerWithAddress; 29 | let rewardToken: FakeERC20; 30 | let incentiveVoting: IncentiveVoting; 31 | let farming: Farming; 32 | let firstFarmTkn: FakeERC20; 33 | let secondFarmTkn: FakeERC20; 34 | let startTime: BigNumber; 35 | 36 | const networkSnapshotter = new NetworkSnapshotter(); 37 | 38 | const setStartContractTimestamp = async () => { 39 | // set right time 40 | await setTimestamp(startTime.toNumber() + 1); 41 | }; 42 | 43 | before("setup Incentive Voting", async () => { 44 | [deployer, signer1] = await ethers.getSigners(); 45 | const IncentiveVoting = await ethers.getContractFactory("IncentiveVoting"); 46 | const Farming = await ethers.getContractFactory("Farming"); 47 | const FakeToken = await ethers.getContractFactory("FakeERC20"); 48 | 49 | startTime = await getNextTimestampDivisibleBy(100); 50 | 51 | // deploy contracts 52 | incentiveVoting = (await upgrades.deployProxy(IncentiveVoting, [startTime])) as IncentiveVoting; 53 | await incentiveVoting.deployed(); 54 | // start contract 55 | await setStartContractTimestamp(); 56 | 57 | rewardToken = await FakeToken.connect(deployer).deploy("Token", "Reward"); 58 | 59 | await rewardToken.deployed(); 60 | farming = (await upgrades.deployProxy(Farming, [ 61 | rewardToken.address, 62 | incentiveVoting.address, 63 | ])) as Farming; 64 | await farming.deployed(); 65 | firstFarmTkn = await FakeToken.connect(deployer).deploy("First Farming", "First"); 66 | await firstFarmTkn.deployed(); 67 | secondFarmTkn = await FakeToken.connect(deployer).deploy("Second Farming", "Second"); 68 | await secondFarmTkn.deployed(); 69 | 70 | const mintAmount = BigNumber.from("1000000").mul(tenPow18); 71 | // mint reward tokens 72 | await rewardToken.mint(deployer.address, mintAmount); 73 | await rewardToken.mint(signer1.address, mintAmount); 74 | // approve rewards 75 | await rewardToken.connect(deployer).approve(incentiveVoting.address, MaxUint256); 76 | await rewardToken.connect(signer1).approve(incentiveVoting.address, MaxUint256); 77 | 78 | // set Farming contract 79 | await incentiveVoting.setFarming( 80 | farming.address, 81 | [firstFarmTkn.address, secondFarmTkn.address], 82 | [AddressZero, AddressZero] 83 | ); 84 | 85 | await networkSnapshotter.firstSnapshot(); 86 | }); 87 | 88 | afterEach("revert", async () => await networkSnapshotter.revert()); 89 | 90 | describe("# initial checks", () => { 91 | it("initial contract addresses", async () => { 92 | expect(await incentiveVoting.farming()).to.be.equal(farming.address); 93 | expect(await incentiveVoting.rewardToken()).to.be.equal(rewardToken.address); 94 | expect(await incentiveVoting.tokenBonding()).to.be.equal(AddressZero); 95 | }); 96 | 97 | it("startTime is ok", async () => { 98 | expect(await incentiveVoting.startTime()).to.be.equal(startTime); 99 | }); 100 | 101 | it("approvedTokens work is ok", async () => { 102 | expect(await incentiveVoting.approvedPoolsLength()).to.be.equal(2); 103 | expect(await incentiveVoting.tokenByPid(0)).to.be.equal(firstFarmTkn.address); 104 | expect(await incentiveVoting.tokenByPid(1)).to.be.equal(secondFarmTkn.address); 105 | expect(await incentiveVoting.tokenByPid(2)).to.be.equal(AddressZero); 106 | }); 107 | 108 | it("getWeek is ok", async () => { 109 | expect(await incentiveVoting.getWeek()).to.be.equal(0); 110 | await advanceTime(week.toNumber()); 111 | expect(await incentiveVoting.getWeek()).to.be.equal(1); 112 | }); 113 | 114 | it("cannot set farming second time", async () => { 115 | await expect( 116 | incentiveVoting.setFarming( 117 | farming.address, 118 | [firstFarmTkn.address, secondFarmTkn.address], 119 | [AddressZero, AddressZero] 120 | ) 121 | ).to.eventually.be.rejectedWith(Error, "farming address can be set only once"); 122 | }); 123 | }); 124 | 125 | describe("# addTokenApproval", () => { 126 | it("function can call only owner", async () => { 127 | await expect( 128 | incentiveVoting.connect(signer1).addTokenApproval(AddressZero, AddressZero, false) 129 | ).to.eventually.be.rejectedWith("Ownable: caller is not the owner"); 130 | }); 131 | }); 132 | 133 | describe("# adding/removing reward tokens to contract", () => { 134 | it("cannot add reward for previous week", async () => { 135 | await advanceTime(week.toNumber()); 136 | 137 | await expect(incentiveVoting.addReward(0, 0)).to.eventually.be.rejectedWith( 138 | Error, 139 | "You can add rewards starting from the current week" 140 | ); 141 | }); 142 | 143 | it("addReward works as expected", async () => { 144 | const currentWeek = await incentiveVoting.getWeek(); 145 | const nextWeek = currentWeek.add(1); 146 | 147 | const currentWeekAmount = BigNumber.from("1000").mul(tenPow18); 148 | const nextWeekAmount = BigNumber.from("2000").mul(tenPow18); 149 | 150 | const currentWeekTotalRewordsBefore = await incentiveVoting.totalRewards(currentWeek); 151 | const nextWeekTotalRewordsBefore = await incentiveVoting.totalRewards(nextWeek); 152 | 153 | await expect(incentiveVoting.connect(signer1).addReward(nextWeek, nextWeekAmount)) 154 | .to.emit(rewardToken, "Transfer") 155 | .and.to.emit(incentiveVoting, "RewardChanged") 156 | .withArgs( 157 | signer1.address, 158 | nextWeek, 159 | nextWeekAmount, 160 | nextWeekTotalRewordsBefore.add(nextWeekAmount) 161 | ); 162 | await expect(incentiveVoting.connect(signer1).addReward(currentWeek, currentWeekAmount)) 163 | .to.emit(rewardToken, "Transfer") 164 | .and.to.emit(incentiveVoting, "RewardChanged") 165 | .withArgs( 166 | signer1.address, 167 | currentWeek, 168 | currentWeekAmount, 169 | currentWeekTotalRewordsBefore.add(currentWeekAmount) 170 | ); 171 | 172 | expect(await incentiveVoting.totalRewards(currentWeek)).to.be.equal( 173 | currentWeekTotalRewordsBefore.add(currentWeekAmount) 174 | ); 175 | expect(await incentiveVoting.totalRewards(nextWeek)).to.be.equal( 176 | nextWeekTotalRewordsBefore.add(nextWeekAmount) 177 | ); 178 | 179 | const currentWeekPlusAmount = BigNumber.from("500").mul(tenPow18); 180 | await expect(incentiveVoting.connect(deployer).addReward(currentWeek, currentWeekPlusAmount)) 181 | .to.emit(rewardToken, "Transfer") 182 | .and.to.emit(incentiveVoting, "RewardChanged") 183 | .withArgs( 184 | deployer.address, 185 | currentWeek, 186 | currentWeekPlusAmount, 187 | currentWeekTotalRewordsBefore.add(currentWeekAmount).add(currentWeekPlusAmount) 188 | ); 189 | 190 | expect(await incentiveVoting.totalRewards(currentWeek)).to.be.equal( 191 | currentWeekTotalRewordsBefore.add(currentWeekAmount).add(currentWeekPlusAmount) 192 | ); 193 | }); 194 | 195 | it("removeReward can call only owner", async () => { 196 | await expect( 197 | incentiveVoting.connect(signer1).removeReward(0, 0) 198 | ).to.eventually.be.rejectedWith(Error, "Ownable: caller is not the owner"); 199 | }); 200 | 201 | it("removeReward can be called starting from the next week", async () => { 202 | const currentWeek = await incentiveVoting.getWeek(); 203 | await expect( 204 | incentiveVoting.connect(deployer).removeReward(currentWeek, 0) 205 | ).to.eventually.be.rejectedWith(Error, "You can remove rewards starting from the next week"); 206 | }); 207 | 208 | it("cannot remove rewards if amount is more than reward for that week", async () => { 209 | const arithmeticErrorCode = "0x11"; 210 | 211 | const currentWeek = await incentiveVoting.getWeek(); 212 | const nextWeek = currentWeek.add(1); 213 | 214 | await expect( 215 | incentiveVoting.connect(deployer).removeReward(nextWeek, 1) 216 | ).to.eventually.be.rejectedWith(Error, arithmeticErrorCode); 217 | }); 218 | 219 | it("removeReward works as expected", async () => { 220 | const currentWeek = await incentiveVoting.getWeek(); 221 | const nextWeek = currentWeek.add(1); 222 | 223 | const amount = BigNumber.from("1000").mul(tenPow18); 224 | 225 | await incentiveVoting.connect(deployer).addReward(nextWeek, amount); 226 | 227 | const nextWeekTotalRewordsBefore = await incentiveVoting.totalRewards(nextWeek); 228 | 229 | await expect(incentiveVoting.connect(deployer).removeReward(nextWeek, amount)) 230 | .to.emit(rewardToken, "Transfer") 231 | .and.to.emit(incentiveVoting, "RewardChanged") 232 | .withArgs( 233 | deployer.address, 234 | nextWeek, 235 | amount.mul("-1"), 236 | nextWeekTotalRewordsBefore.sub(amount) 237 | ); 238 | 239 | expect(await incentiveVoting.totalRewards(nextWeek)).to.be.equal( 240 | nextWeekTotalRewordsBefore.sub(amount) 241 | ); 242 | }); 243 | }); 244 | 245 | describe("# voting", () => { 246 | it("should fail if tokens length is not equal to votes length", async () => { 247 | await expect(incentiveVoting.connect(deployer).vote([0], [])).to.eventually.be.rejectedWith( 248 | Error, 249 | "Input length mismatch" 250 | ); 251 | }); 252 | 253 | it("should fail if pid does not exist", async () => { 254 | await expect(incentiveVoting.connect(deployer).vote([2], [0])).to.eventually.be.rejectedWith( 255 | Error, 256 | "Not approved for incentives" 257 | ); 258 | }); 259 | 260 | it("voting works as expected", async () => { 261 | const currentWeek = await incentiveVoting.getWeek(); 262 | const voteAmount = BigNumber.from("10000"); 263 | 264 | const userVotesBefore = await incentiveVoting.userVotes(deployer.address, currentWeek); 265 | const tokenVotesBefore = await incentiveVoting.pidVotes(0, currentWeek); 266 | const totalVotesBefore = await incentiveVoting.totalVotes(currentWeek); 267 | const userTokenVotesBefore = await incentiveVoting.userPidVotes( 268 | deployer.address, 269 | 0, 270 | currentWeek 271 | ); 272 | 273 | await expect(incentiveVoting.connect(deployer).vote([0], [voteAmount])).to.emit( 274 | incentiveVoting, 275 | "VotedForIncentives" 276 | ); 277 | 278 | expect(await incentiveVoting.userVotes(deployer.address, currentWeek)).to.be.equal( 279 | userVotesBefore.add(voteAmount) 280 | ); 281 | expect(await incentiveVoting.pidVotes(0, currentWeek)).to.be.equal( 282 | tokenVotesBefore.add(voteAmount) 283 | ); 284 | expect(await incentiveVoting.totalVotes(currentWeek)).to.be.equal( 285 | totalVotesBefore.add(voteAmount) 286 | ); 287 | expect(await incentiveVoting.userPidVotes(deployer.address, 0, currentWeek)).to.be.equal( 288 | userTokenVotesBefore.add(voteAmount) 289 | ); 290 | }); 291 | 292 | it("voting batch works as expected", async () => { 293 | const currentWeek = await incentiveVoting.getWeek(); 294 | const firstVoteAmount = BigNumber.from("10000"); 295 | const secondVoteAmount = BigNumber.from("20000"); 296 | 297 | const userVotesBefore = await incentiveVoting.userVotes(deployer.address, currentWeek); 298 | const firstTknVotesBefore = await incentiveVoting.pidVotes(0, currentWeek); 299 | const secondTknVotesBefore = await incentiveVoting.pidVotes(0, currentWeek); 300 | const totalVotesBefore = await incentiveVoting.totalVotes(currentWeek); 301 | const userFirstTknVotesBefore = await incentiveVoting.userPidVotes( 302 | deployer.address, 303 | 0, 304 | currentWeek 305 | ); 306 | const userSecondTknVotesBefore = await incentiveVoting.userPidVotes( 307 | deployer.address, 308 | 0, 309 | currentWeek 310 | ); 311 | 312 | const tokens = [0, 1]; 313 | const votes = [firstVoteAmount, secondVoteAmount]; 314 | const votesSum = votes.reduce((prev, curr) => prev.add(curr), BigNumber.from(0)); 315 | await expect(incentiveVoting.connect(deployer).vote(tokens, votes)).to.emit( 316 | incentiveVoting, 317 | "VotedForIncentives" 318 | ); 319 | 320 | expect(await incentiveVoting.userVotes(deployer.address, currentWeek)).to.be.equal( 321 | userVotesBefore.add(votesSum) 322 | ); 323 | expect(await incentiveVoting.pidVotes(0, currentWeek)).to.be.equal( 324 | firstTknVotesBefore.add(firstVoteAmount) 325 | ); 326 | expect(await incentiveVoting.pidVotes(1, currentWeek)).to.be.equal( 327 | secondTknVotesBefore.add(secondVoteAmount) 328 | ); 329 | expect(await incentiveVoting.totalVotes(currentWeek)).to.be.equal( 330 | totalVotesBefore.add(votesSum) 331 | ); 332 | expect(await incentiveVoting.userPidVotes(deployer.address, 0, currentWeek)).to.be.equal( 333 | userFirstTknVotesBefore.add(firstVoteAmount) 334 | ); 335 | expect(await incentiveVoting.userPidVotes(deployer.address, 1, currentWeek)).to.be.equal( 336 | userSecondTknVotesBefore.add(secondVoteAmount) 337 | ); 338 | }); 339 | }); 340 | 341 | describe("# getRewardsPerSecond", async () => { 342 | it("if week equals to zero than reward per second is 0", async () => { 343 | expect(await incentiveVoting.getRewardsPerSecond(0, 0)).to.be.equal(0); 344 | }); 345 | 346 | it("if there is no vote for that token than reward per second is 0", async () => { 347 | expect(await incentiveVoting.getRewardsPerSecond(0, 1)).to.be.equal(0); 348 | }); 349 | 350 | it("returns 0 if rewards for that week is 0", async () => { 351 | const firstVoteAmount = BigNumber.from("10000"); 352 | const secondVoteAmount = BigNumber.from("20000"); 353 | const tokens = [0, 1]; 354 | const votes = [firstVoteAmount, secondVoteAmount]; 355 | await incentiveVoting.connect(deployer).vote(tokens, votes); 356 | 357 | expect(await incentiveVoting.getRewardsPerSecond(0, 1)).to.be.equal(0); 358 | }); 359 | 360 | it("function works as expected", async () => { 361 | const currentWeek = await incentiveVoting.getWeek(); 362 | const rewardAmount = BigNumber.from("1000").mul(tenPow18); 363 | await incentiveVoting.connect(deployer).addReward(currentWeek, rewardAmount); 364 | 365 | const firstVoteAmount = BigNumber.from("10000"); 366 | const secondVoteAmount = BigNumber.from("20000"); 367 | const tokens = [0, 1]; 368 | const votes = [firstVoteAmount, secondVoteAmount]; 369 | const votesSum = votes.reduce((prev, curr) => prev.add(curr), BigNumber.from(0)); 370 | await incentiveVoting.connect(deployer).vote(tokens, votes); 371 | 372 | const expectedRewardsPerSecond = rewardAmount.mul(firstVoteAmount).div(votesSum.mul(week)); 373 | 374 | expect(await incentiveVoting.getRewardsPerSecond(0, currentWeek.add(1))).to.be.equal( 375 | expectedRewardsPerSecond 376 | ); 377 | }); 378 | }); 379 | 380 | describe("getter functions", () => { 381 | it("getVotes, getUserVotes, availableVotes work correctly", async () => { 382 | // get week 383 | const currentWeek = await incentiveVoting.getWeek(); 384 | // vote 385 | const firstVoteAmount = BigNumber.from("7000"); 386 | const secondVoteAmount = BigNumber.from("5000"); 387 | const tokens = [0, 1]; 388 | const votes = [firstVoteAmount, secondVoteAmount]; 389 | const votesSum = votes.reduce((prev, curr) => prev.add(curr), BigNumber.from(0)); 390 | await incentiveVoting.connect(deployer).vote(tokens, votes); 391 | 392 | // getVotes 393 | const weekVotes = await incentiveVoting.getVotes(currentWeek); 394 | expect(weekVotes._totalVotes).to.be.equal(votesSum); 395 | expect(weekVotes._voteData[0].token).to.be.equal(firstFarmTkn.address); 396 | expect(weekVotes._voteData[0].votes).to.be.equal(firstVoteAmount); 397 | expect(weekVotes._voteData[1].token).to.be.equal(secondFarmTkn.address); 398 | expect(weekVotes._voteData[1].votes).to.be.equal(secondVoteAmount); 399 | 400 | // getUserVotes 401 | const userWeekVotes = await incentiveVoting.getUserVotes(deployer.address, currentWeek); 402 | expect(userWeekVotes._totalVotes).to.be.equal(votesSum); 403 | expect(userWeekVotes._voteData[0].token).to.be.equal(firstFarmTkn.address); 404 | expect(userWeekVotes._voteData[0].votes).to.be.equal(firstVoteAmount); 405 | expect(userWeekVotes._voteData[1].token).to.be.equal(secondFarmTkn.address); 406 | expect(userWeekVotes._voteData[1].votes).to.be.equal(secondVoteAmount); 407 | }); 408 | }); 409 | }); 410 | -------------------------------------------------------------------------------- /.openzeppelin/unknown-31337.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": "3.2", 3 | "admin": { 4 | "address": "0xa2F6E6029638cCb484A2ccb6414499aD3e825CaC", 5 | "txHash": "0xc0523be0d8372c2e84681bd5fd59bf3f2c7b3decc54589ad2447665daf5a08dd" 6 | }, 7 | "proxies": [ 8 | { 9 | "address": "0x617d3b3743c3774d75c11141a48C9A414E777871", 10 | "txHash": "0xb5e62e3a50a1730a08f8253c8d2b783dd02853e82d9e14a3679b8c33c4da672e", 11 | "kind": "transparent" 12 | }, 13 | { 14 | "address": "0x19FC40E83990dC7c262F18e9C4BCCDB10ad06D0D", 15 | "txHash": "0xd0bfc6081f4251abd2796a0504f86b4c985cbcea6b00725a5caaa5e654b9b485", 16 | "kind": "transparent" 17 | }, 18 | { 19 | "address": "0x466C2C8c3d3aF0c55cf80146fAB394953215b2AC", 20 | "txHash": "0x3d8dc30ef6f524bbc9380c4f7bf7747cc2b5b304b311130c37f6ab009720e50b", 21 | "kind": "transparent" 22 | }, 23 | { 24 | "address": "0xB98aEf45544ACFd5A6cD2659b6e61Ce0f003Ae58", 25 | "txHash": "0x42dc56b1a642d1501adfbcee5825cceba4ed2365ef54a9407905da50de332785", 26 | "kind": "transparent" 27 | }, 28 | { 29 | "address": "0xD2547e4AA4f5a2b0a512BFd45C9E3360FeEa48Df", 30 | "txHash": "0x1b83a28df9b3ab8797792e983da9af92c50cfaf5aa8476efb9596af5ec79e12c", 31 | "kind": "transparent" 32 | } 33 | ], 34 | "impls": { 35 | "d0a54d647b546e1963a00cbce142701d38b128ac74624424a5f74effdfcc26f0": { 36 | "address": "0x205E7ae911dBf69a482C27A5BD6ca152C9586615", 37 | "txHash": "0xe5f0295f95c95437315dff4c92eb72fc1f2531e090224fb599ad5ec2f56a77a0", 38 | "layout": { 39 | "storage": [ 40 | { 41 | "label": "_initialized", 42 | "offset": 0, 43 | "slot": "0", 44 | "type": "t_uint8", 45 | "contract": "Initializable", 46 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", 47 | "retypedFrom": "bool" 48 | }, 49 | { 50 | "label": "_initializing", 51 | "offset": 1, 52 | "slot": "0", 53 | "type": "t_bool", 54 | "contract": "Initializable", 55 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" 56 | }, 57 | { 58 | "label": "_status", 59 | "offset": 0, 60 | "slot": "1", 61 | "type": "t_uint256", 62 | "contract": "ReentrancyGuardUpgradeable", 63 | "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" 64 | }, 65 | { 66 | "label": "__gap", 67 | "offset": 0, 68 | "slot": "2", 69 | "type": "t_array(t_uint256)49_storage", 70 | "contract": "ReentrancyGuardUpgradeable", 71 | "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:74" 72 | }, 73 | { 74 | "label": "__gap", 75 | "offset": 0, 76 | "slot": "51", 77 | "type": "t_array(t_uint256)50_storage", 78 | "contract": "ContextUpgradeable", 79 | "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" 80 | }, 81 | { 82 | "label": "_owner", 83 | "offset": 0, 84 | "slot": "101", 85 | "type": "t_address", 86 | "contract": "OwnableUpgradeable", 87 | "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" 88 | }, 89 | { 90 | "label": "__gap", 91 | "offset": 0, 92 | "slot": "102", 93 | "type": "t_array(t_uint256)49_storage", 94 | "contract": "OwnableUpgradeable", 95 | "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" 96 | }, 97 | { 98 | "label": "poolInfo", 99 | "offset": 0, 100 | "slot": "151", 101 | "type": "t_array(t_struct(PoolInfo)3146_storage)dyn_storage", 102 | "contract": "Farming", 103 | "src": "contracts/Farming.sol:37" 104 | }, 105 | { 106 | "label": "userInfo", 107 | "offset": 0, 108 | "slot": "152", 109 | "type": "t_mapping(t_uint256,t_mapping(t_address,t_struct(UserInfo)3133_storage))", 110 | "contract": "Farming", 111 | "src": "contracts/Farming.sol:40" 112 | }, 113 | { 114 | "label": "startTime", 115 | "offset": 0, 116 | "slot": "153", 117 | "type": "t_uint256", 118 | "contract": "Farming", 119 | "src": "contracts/Farming.sol:42" 120 | }, 121 | { 122 | "label": "claimReceiver", 123 | "offset": 0, 124 | "slot": "154", 125 | "type": "t_mapping(t_address,t_address)", 126 | "contract": "Farming", 127 | "src": "contracts/Farming.sol:47" 128 | }, 129 | { 130 | "label": "blockThirdPartyActions", 131 | "offset": 0, 132 | "slot": "155", 133 | "type": "t_mapping(t_address,t_bool)", 134 | "contract": "Farming", 135 | "src": "contracts/Farming.sol:51" 136 | }, 137 | { 138 | "label": "rewardToken", 139 | "offset": 0, 140 | "slot": "156", 141 | "type": "t_contract(IERC20Upgradeable)546", 142 | "contract": "Farming", 143 | "src": "contracts/Farming.sol:53" 144 | }, 145 | { 146 | "label": "incentiveVoting", 147 | "offset": 0, 148 | "slot": "157", 149 | "type": "t_contract(IIncentiveVoting)8497", 150 | "contract": "Farming", 151 | "src": "contracts/Farming.sol:54" 152 | } 153 | ], 154 | "types": { 155 | "t_address": { 156 | "label": "address", 157 | "numberOfBytes": "20" 158 | }, 159 | "t_array(t_struct(PoolInfo)3146_storage)dyn_storage": { 160 | "label": "struct Farming.PoolInfo[]", 161 | "numberOfBytes": "32" 162 | }, 163 | "t_array(t_uint256)49_storage": { 164 | "label": "uint256[49]", 165 | "numberOfBytes": "1568" 166 | }, 167 | "t_array(t_uint256)50_storage": { 168 | "label": "uint256[50]", 169 | "numberOfBytes": "1600" 170 | }, 171 | "t_bool": { 172 | "label": "bool", 173 | "numberOfBytes": "1" 174 | }, 175 | "t_contract(IERC20Upgradeable)546": { 176 | "label": "contract IERC20Upgradeable", 177 | "numberOfBytes": "20" 178 | }, 179 | "t_contract(IIncentiveVoting)8497": { 180 | "label": "contract IIncentiveVoting", 181 | "numberOfBytes": "20" 182 | }, 183 | "t_contract(IStrategy)9072": { 184 | "label": "contract IStrategy", 185 | "numberOfBytes": "20" 186 | }, 187 | "t_mapping(t_address,t_address)": { 188 | "label": "mapping(address => address)", 189 | "numberOfBytes": "32" 190 | }, 191 | "t_mapping(t_address,t_bool)": { 192 | "label": "mapping(address => bool)", 193 | "numberOfBytes": "32" 194 | }, 195 | "t_mapping(t_address,t_struct(UserInfo)3133_storage)": { 196 | "label": "mapping(address => struct Farming.UserInfo)", 197 | "numberOfBytes": "32" 198 | }, 199 | "t_mapping(t_uint256,t_mapping(t_address,t_struct(UserInfo)3133_storage))": { 200 | "label": "mapping(uint256 => mapping(address => struct Farming.UserInfo))", 201 | "numberOfBytes": "32" 202 | }, 203 | "t_struct(PoolInfo)3146_storage": { 204 | "label": "struct Farming.PoolInfo", 205 | "members": [ 206 | { 207 | "label": "token", 208 | "type": "t_contract(IERC20Upgradeable)546", 209 | "offset": 0, 210 | "slot": "0" 211 | }, 212 | { 213 | "label": "strategy", 214 | "type": "t_contract(IStrategy)9072", 215 | "offset": 0, 216 | "slot": "1" 217 | }, 218 | { 219 | "label": "rewardsPerSecond", 220 | "type": "t_uint256", 221 | "offset": 0, 222 | "slot": "2" 223 | }, 224 | { 225 | "label": "lastRewardTime", 226 | "type": "t_uint256", 227 | "offset": 0, 228 | "slot": "3" 229 | }, 230 | { 231 | "label": "accRewardPerShare", 232 | "type": "t_uint256", 233 | "offset": 0, 234 | "slot": "4" 235 | } 236 | ], 237 | "numberOfBytes": "160" 238 | }, 239 | "t_struct(UserInfo)3133_storage": { 240 | "label": "struct Farming.UserInfo", 241 | "members": [ 242 | { 243 | "label": "shares", 244 | "type": "t_uint256", 245 | "offset": 0, 246 | "slot": "0" 247 | }, 248 | { 249 | "label": "rewardDebt", 250 | "type": "t_uint256", 251 | "offset": 0, 252 | "slot": "1" 253 | }, 254 | { 255 | "label": "claimable", 256 | "type": "t_uint256", 257 | "offset": 0, 258 | "slot": "2" 259 | } 260 | ], 261 | "numberOfBytes": "96" 262 | }, 263 | "t_uint256": { 264 | "label": "uint256", 265 | "numberOfBytes": "32" 266 | }, 267 | "t_uint8": { 268 | "label": "uint8", 269 | "numberOfBytes": "1" 270 | } 271 | } 272 | } 273 | }, 274 | "54c41f47c3c741673592df311ae6739a14bbf38bded0e4e7abbd37ff8ee00013": { 275 | "address": "0x2B2f78c5BF6D9C12Ee1225D5F374aa91204580c3", 276 | "txHash": "0xb5d182b6024b685726b2852a6569c5f534c22396b193a57a33a96f2f12aac47b", 277 | "layout": { 278 | "storage": [ 279 | { 280 | "label": "_initialized", 281 | "offset": 0, 282 | "slot": "0", 283 | "type": "t_uint8", 284 | "contract": "Initializable", 285 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", 286 | "retypedFrom": "bool" 287 | }, 288 | { 289 | "label": "_initializing", 290 | "offset": 1, 291 | "slot": "0", 292 | "type": "t_bool", 293 | "contract": "Initializable", 294 | "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" 295 | }, 296 | { 297 | "label": "__gap", 298 | "offset": 0, 299 | "slot": "1", 300 | "type": "t_array(t_uint256)50_storage", 301 | "contract": "ContextUpgradeable", 302 | "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" 303 | }, 304 | { 305 | "label": "_owner", 306 | "offset": 0, 307 | "slot": "51", 308 | "type": "t_address", 309 | "contract": "OwnableUpgradeable", 310 | "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" 311 | }, 312 | { 313 | "label": "__gap", 314 | "offset": 0, 315 | "slot": "52", 316 | "type": "t_array(t_uint256)49_storage", 317 | "contract": "OwnableUpgradeable", 318 | "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" 319 | }, 320 | { 321 | "label": "_status", 322 | "offset": 0, 323 | "slot": "101", 324 | "type": "t_uint256", 325 | "contract": "ReentrancyGuardUpgradeable", 326 | "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" 327 | }, 328 | { 329 | "label": "__gap", 330 | "offset": 0, 331 | "slot": "102", 332 | "type": "t_array(t_uint256)49_storage", 333 | "contract": "ReentrancyGuardUpgradeable", 334 | "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:74" 335 | }, 336 | { 337 | "label": "_paused", 338 | "offset": 0, 339 | "slot": "151", 340 | "type": "t_bool", 341 | "contract": "PausableUpgradeable", 342 | "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" 343 | }, 344 | { 345 | "label": "__gap", 346 | "offset": 0, 347 | "slot": "152", 348 | "type": "t_array(t_uint256)49_storage", 349 | "contract": "PausableUpgradeable", 350 | "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" 351 | }, 352 | { 353 | "label": "pid", 354 | "offset": 0, 355 | "slot": "201", 356 | "type": "t_uint256", 357 | "contract": "StableCoinStrategyCurve", 358 | "src": "contracts/StableCoinStrategyCurve.sol:49" 359 | }, 360 | { 361 | "label": "farmContractAddress", 362 | "offset": 0, 363 | "slot": "202", 364 | "type": "t_address", 365 | "contract": "StableCoinStrategyCurve", 366 | "src": "contracts/StableCoinStrategyCurve.sol:50" 367 | }, 368 | { 369 | "label": "want", 370 | "offset": 0, 371 | "slot": "203", 372 | "type": "t_address", 373 | "contract": "StableCoinStrategyCurve", 374 | "src": "contracts/StableCoinStrategyCurve.sol:51" 375 | }, 376 | { 377 | "label": "cake", 378 | "offset": 0, 379 | "slot": "204", 380 | "type": "t_address", 381 | "contract": "StableCoinStrategyCurve", 382 | "src": "contracts/StableCoinStrategyCurve.sol:52" 383 | }, 384 | { 385 | "label": "token0", 386 | "offset": 0, 387 | "slot": "205", 388 | "type": "t_address", 389 | "contract": "StableCoinStrategyCurve", 390 | "src": "contracts/StableCoinStrategyCurve.sol:53" 391 | }, 392 | { 393 | "label": "token1", 394 | "offset": 0, 395 | "slot": "206", 396 | "type": "t_address", 397 | "contract": "StableCoinStrategyCurve", 398 | "src": "contracts/StableCoinStrategyCurve.sol:54" 399 | }, 400 | { 401 | "label": "router", 402 | "offset": 0, 403 | "slot": "207", 404 | "type": "t_address", 405 | "contract": "StableCoinStrategyCurve", 406 | "src": "contracts/StableCoinStrategyCurve.sol:55" 407 | }, 408 | { 409 | "label": "stableSwap", 410 | "offset": 0, 411 | "slot": "208", 412 | "type": "t_address", 413 | "contract": "StableCoinStrategyCurve", 414 | "src": "contracts/StableCoinStrategyCurve.sol:56" 415 | }, 416 | { 417 | "label": "helioFarming", 418 | "offset": 0, 419 | "slot": "209", 420 | "type": "t_address", 421 | "contract": "StableCoinStrategyCurve", 422 | "src": "contracts/StableCoinStrategyCurve.sol:57" 423 | }, 424 | { 425 | "label": "earnedToStablePath", 426 | "offset": 0, 427 | "slot": "210", 428 | "type": "t_array(t_address)dyn_storage", 429 | "contract": "StableCoinStrategyCurve", 430 | "src": "contracts/StableCoinStrategyCurve.sol:59" 431 | }, 432 | { 433 | "label": "i0", 434 | "offset": 0, 435 | "slot": "211", 436 | "type": "t_uint256", 437 | "contract": "StableCoinStrategyCurve", 438 | "src": "contracts/StableCoinStrategyCurve.sol:61" 439 | }, 440 | { 441 | "label": "i1", 442 | "offset": 0, 443 | "slot": "212", 444 | "type": "t_uint256", 445 | "contract": "StableCoinStrategyCurve", 446 | "src": "contracts/StableCoinStrategyCurve.sol:62" 447 | }, 448 | { 449 | "label": "enableAutoHarvest", 450 | "offset": 0, 451 | "slot": "213", 452 | "type": "t_bool", 453 | "contract": "StableCoinStrategyCurve", 454 | "src": "contracts/StableCoinStrategyCurve.sol:64" 455 | }, 456 | { 457 | "label": "wantLockedTotal", 458 | "offset": 0, 459 | "slot": "214", 460 | "type": "t_uint256", 461 | "contract": "StableCoinStrategyCurve", 462 | "src": "contracts/StableCoinStrategyCurve.sol:66" 463 | }, 464 | { 465 | "label": "sharesTotal", 466 | "offset": 0, 467 | "slot": "215", 468 | "type": "t_uint256", 469 | "contract": "StableCoinStrategyCurve", 470 | "src": "contracts/StableCoinStrategyCurve.sol:67" 471 | }, 472 | { 473 | "label": "minEarnAmount", 474 | "offset": 0, 475 | "slot": "216", 476 | "type": "t_uint256", 477 | "contract": "StableCoinStrategyCurve", 478 | "src": "contracts/StableCoinStrategyCurve.sol:69" 479 | } 480 | ], 481 | "types": { 482 | "t_address": { 483 | "label": "address", 484 | "numberOfBytes": "20" 485 | }, 486 | "t_array(t_address)dyn_storage": { 487 | "label": "address[]", 488 | "numberOfBytes": "32" 489 | }, 490 | "t_array(t_uint256)49_storage": { 491 | "label": "uint256[49]", 492 | "numberOfBytes": "1568" 493 | }, 494 | "t_array(t_uint256)50_storage": { 495 | "label": "uint256[50]", 496 | "numberOfBytes": "1600" 497 | }, 498 | "t_bool": { 499 | "label": "bool", 500 | "numberOfBytes": "1" 501 | }, 502 | "t_uint256": { 503 | "label": "uint256", 504 | "numberOfBytes": "32" 505 | }, 506 | "t_uint8": { 507 | "label": "uint8", 508 | "numberOfBytes": "1" 509 | } 510 | } 511 | } 512 | } 513 | } 514 | } 515 | --------------------------------------------------------------------------------